001    package org.apache.fulcrum.yaafi.service.shutdown;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.ByteArrayOutputStream;
023    import java.io.File;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.OutputStream;
027    import java.security.MessageDigest;
028    
029    import org.apache.avalon.framework.logger.Logger;
030    import org.apache.fulcrum.yaafi.framework.util.InputStreamLocator;
031    
032    /**
033     * Monitors a resource and checks if it has changed
034     *
035     * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
036     */
037    
038    public class ShutdownEntry
039    {
040        /** buffer size for copy() */
041        private static final int BUF_SIZE = 1024;
042    
043        /** the location to monitor for changes */
044        private String location;
045    
046        /** the last message digest of the location */
047        private byte[] digest;
048    
049        /** the locator to load the monitored resource */
050        private InputStreamLocator locator;
051    
052        /** keep a notice for the very first invocation */
053        private boolean isFirstInvocation;
054    
055        /** the logger to be used */
056        private Logger logger;
057    
058        /** use System.exit() to shutdown the JVM */
059        private boolean useSystemExit;
060    
061        /**
062         * Constructor
063         *
064         * @param logger the logger to use
065         * @param applicationDir the home directory of the application
066         * @param location the location to monitor for changes
067         * @param useSystemExit use System.exit() on shutdown
068         */
069        public ShutdownEntry( Logger logger, File applicationDir, String location, boolean useSystemExit )
070        {
071            this.isFirstInvocation = true;
072            this.useSystemExit = useSystemExit;
073            this.location = location;
074            this.locator  = new InputStreamLocator( applicationDir );
075            this.logger = logger;
076        }
077    
078        /**
079         * @return has the monitored location changed
080         */
081        public boolean hasChanged()
082        {
083            boolean result = false;
084            InputStream is = null;
085            byte[] currDigest = null;
086    
087            try
088            {
089                // get a grip on our resource
090    
091                is = this.locate();
092    
093                if( is == null )
094                {
095                    String msg = "Unable to find the following resource : " + this.getLocation();
096                    this.getLogger().warn(msg);
097                }
098                else
099                {
100                    // calculate a SHA-1 digest
101    
102                    currDigest = this.getDigest(is);
103                    is.close();
104                    is = null;
105    
106                    if( this.isFirstInvocation() == true )
107                    {
108                        isFirstInvocation = false;
109                        this.getLogger().debug( "Storing SHA-1 digest of " + this.getLocation() );
110                        this.setDigest( currDigest );
111                    }
112                    else
113                    {
114                        if( equals( this.digest, currDigest ) == false )
115                        {
116                            this.getLogger().debug( "The following resource has changed : " + this.getLocation() );
117                            this.setDigest( currDigest );
118                            result = true;
119                        }
120                    }
121                }
122    
123                return result;
124            }
125            catch(Exception e)
126            {
127                String msg = "The ShutdownService encountered an internal error";
128                this.getLogger().error(msg,e);
129                return false;
130            }
131            finally
132            {
133                if( is != null )
134                {
135                    try
136                    {
137                        is.close();
138                    }
139                    catch (Exception e)
140                    {
141                        String msg = "Can't close the InputStream during error recovery";
142                        this.getLogger().error(msg,e);
143                    }
144                }
145            }
146    
147        }
148    
149        /**
150         * @return Returns the useSystemExit.
151         */
152        public boolean isUseSystemExit()
153        {
154            return useSystemExit;
155        }
156    
157        /**
158         * @return Returns the isFirstInvocation.
159         */
160        private boolean isFirstInvocation()
161        {
162            return isFirstInvocation;
163        }
164    
165        /**
166         * @return Returns the location.
167         */
168        private String getLocation()
169        {
170            return location;
171        }
172    
173        /**
174         * @return Returns the locator.
175         */
176        private InputStreamLocator getLocator()
177        {
178            return locator;
179        }
180    
181        /**
182         * Creates an InputStream
183         */
184        public InputStream locate() throws IOException
185        {
186            return this.getLocator().locate(this.getLocation());
187        }
188    
189        /**
190         * Creates a message digest
191         */
192        private byte[] getDigest( InputStream is )
193            throws Exception
194        {
195            byte[] result = null;
196            byte[] content = null;
197    
198            ByteArrayOutputStream baos = new ByteArrayOutputStream();
199            copy( is, baos );
200            content = baos.toByteArray();
201            baos.close();
202    
203            MessageDigest sha1 = MessageDigest.getInstance( "SHA1" );
204            sha1.update( content );
205            result = sha1.digest();
206    
207            return result;
208        }
209    
210        /**
211         * @param digest The digest to set.
212         */
213        private void setDigest(byte [] digest)
214        {
215            this.digest = digest;
216        }
217    
218        /**
219         * Compares two byte[] for equality
220         */
221        private static boolean equals(byte[] lhs, byte[] rhs)
222        {
223            if( lhs == rhs )
224            {
225                return true;
226            }
227            else if( lhs.length != rhs.length )
228            {
229                return false;
230            }
231            else
232            {
233                for( int i=0; i<lhs.length; i++ )
234                {
235                    if( lhs[i] != rhs[i] )
236                    {
237                        return false;
238                    }
239                }
240            }
241    
242            return true;
243        }
244    
245        /**
246         * Pumps the input stream to the output stream.
247         *
248         * @param is the source input stream
249         * @param os the target output stream
250         * @throws IOException the copying failed
251         */
252        private static void copy( InputStream is, OutputStream os )
253            throws IOException
254        {
255            byte[] buf = new byte[BUF_SIZE];
256            int n = 0;
257            int total = 0;
258    
259            while ((n = is.read(buf)) > 0)
260            {
261                os.write(buf, 0, n);
262                total += n;
263            }
264    
265            is.close();
266    
267            os.flush();
268            os.close();
269        }
270    
271        /**
272         * @return Returns the logger.
273         */
274        private Logger getLogger()
275        {
276            return logger;
277        }
278    }