001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/portal/owswatch/ServiceWatcher.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    
037    package org.deegree.portal.owswatch;
038    
039    import java.io.File;
040    import java.io.FileReader;
041    import java.io.Serializable;
042    import java.util.ArrayList;
043    import java.util.Calendar;
044    import java.util.HashMap;
045    import java.util.Hashtable;
046    import java.util.Iterator;
047    import java.util.List;
048    import java.util.Map;
049    import java.util.Set;
050    import java.util.TreeMap;
051    
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.framework.mail.EMailMessage;
055    import org.deegree.framework.mail.MailHelper;
056    import org.deegree.framework.mail.MailMessage;
057    import org.deegree.framework.util.StringTools;
058    import org.deegree.framework.xml.XMLTools;
059    import org.deegree.portal.owswatch.configs.OwsWatchConfig;
060    import org.deegree.portal.owswatch.configs.User;
061    import org.w3c.dom.Document;
062    import org.w3c.dom.Element;
063    
064    /**
065     * Mail class of this framework. Its called upon starting tomcat to start watching services and test them regularly
066     * according to their test intervals and logs the result through its service invoker
067     *
068     * @author <a href="mailto:elmasry@lat-lon.de">Moataz Elmasry</a>
069     * @author last edited by: $Author: jmays $
070     *
071     * @version $Revision: 20271 $, $Date: 2009-10-21 13:07:15 +0200 (Mi, 21. Okt 2009) $
072     */
073    public class ServiceWatcher extends Thread implements Serializable {
074    
075        private static final long serialVersionUID = -5247964268533099679L;
076    
077        private static final ILogger LOG = LoggerFactory.getLogger( ServiceWatcher.class );
078    
079        private volatile int minTestInterval = 0;
080    
081        private volatile boolean threadsuspended = true;
082    
083        private volatile int leastWaitTime = 0;
084    
085        // Used to save the time, when the leastWaitTime was taken.
086        private volatile Calendar calLeastTime = null;
087    
088        /**
089         * This hash table holds all ServiceMonitors in the form <serviceHashcode,ServiceMonitor>
090         */
091        static private TreeMap<Integer, ServiceConfiguration> services = new TreeMap<Integer, ServiceConfiguration>();
092    
093        /**
094         * Integer Service id ServiceLog Logger class
095         */
096        private Map<ServiceConfiguration, ServiceLog> serviceLogs = new HashMap<ServiceConfiguration, ServiceLog>();
097    
098        /**
099         * This hash table holds the time of the next run for the given Services in the form <ServiceHashcode,Time remaining
100         * for the next run>
101         */
102        private Hashtable<Integer, Integer> activeServices = new Hashtable<Integer, Integer>( 30 );
103    
104        /**
105         * This function will be called at the begining of the run() method
106         */
107        private void init() {
108    
109            Iterator<ServiceConfiguration> it = services.values().iterator();
110            if ( it.hasNext() ) {
111                threadsuspended = false;
112            }
113        }
114    
115        /*
116         * (non-Javadoc)
117         *
118         * @see java.lang.Thread#run()
119         */
120        @Override
121        public void run() {
122    
123            init();
124            // This will cause the function to start at hours:minutes:00 seconds
125            // Where minutes, should be :10, :15, :30, :60
126            try {
127                Calendar cal = Calendar.getInstance();
128                cal.add( Calendar.MINUTE, 1 );
129                cal.set( Calendar.SECOND, 60 );
130                sleep( ( Calendar.getInstance().getTimeInMillis() - calLeastTime.getTimeInMillis() ) );
131            } catch ( InterruptedException e ) {
132                // Nothing. Its possible to be interrupted
133            }
134            Calendar nextCall = Calendar.getInstance();
135            while ( true ) {
136                try {
137                    synchronized ( this ) {
138                        while ( threadsuspended ) {
139                            wait( 1000 );
140                        }
141                        // In case the wait is interrupted, we guarantee that the test will not be
142                        // executed until its 00 seconds
143                        if ( Calendar.getInstance().get( Calendar.SECOND ) != 0 ) {
144                            int seconds = Calendar.getInstance().get( Calendar.SECOND );
145                            sleep( ( 60 - seconds ) * 1000 );
146                        }
147                        refreshAllServices();
148                        // if true then we have to shift the time system less than one minute forward
149                        // to make the seconds 00 again
150                        // minTestInterval = findMinTestInterval();
151                        nextCall.add( Calendar.MINUTE, 1 );
152                        long sleepTime = nextCall.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();
153                        wait( sleepTime );
154                    }
155                } catch ( InterruptedException e ) {
156                    // Nothing. Its possible to be interrupted
157                }
158            }
159        }
160    
161        /**
162         * adds a new Service to the ServiceList
163         *
164         * @param service
165         * @param servicelog
166         */
167        public synchronized void addService( ServiceConfiguration service, ServiceLog servicelog ) {
168    
169            if ( !services.containsKey( Integer.valueOf( service.getServiceid() ) ) ) {
170                serviceLogs.put( service, servicelog );
171            } else {
172                ServiceLog log = serviceLogs.remove( services.get( Integer.valueOf( service.getServiceid() ) ) );
173                serviceLogs.put( service, log );
174            }
175            services.put( Integer.valueOf( service.getServiceid() ), service );
176    
177            if ( service.isActive() ) {
178                addActiveService( service );
179            }
180        }
181    
182        /**
183         * Adds an active service to the list of active services. It also sets when the first test should take place. This
184         * is useful to start tests at :00 :15 :30 minutes etc..
185         *
186         * @param service
187         */
188        protected synchronized void addActiveService( ServiceConfiguration service ) {
189    
190            int serviceId = service.getServiceid();
191            Calendar cal = Calendar.getInstance();
192            // Use the minimum refreshRate as a refreshRate for the watcher thread
193            if ( minTestInterval == 0 || service.getRefreshRate() < minTestInterval ) {
194                minTestInterval = service.getRefreshRate();
195            }
196            int waitTime = 0;
197            // The checks are made in descending order, so that the bigger number takes precedence
198            if ( service.getRefreshRate() % 60 == 0 ) {
199                // if the test takes place every hour or multiple of hour
200                waitTime = 60 - cal.get( Calendar.MINUTE );
201            } else if ( service.getRefreshRate() % 30 == 0 ) {
202                // if the test takes place every half an hour
203                waitTime = 30 - ( cal.get( Calendar.MINUTE ) % 30 );
204            } else if ( service.getRefreshRate() % 15 == 0 ) {
205                // if the test takes place every 15 minutes
206                waitTime = 15 - ( cal.get( Calendar.MINUTE ) % 15 );
207            } else if ( service.getRefreshRate() % 10 == 0 ) {
208                // if the test takes place every 10 mins
209                waitTime = 10 - ( cal.get( Calendar.MINUTE ) % 10 );
210            } else {
211                // This one is actually thought for tests every one minute,
212                // but it will of course cover any other number
213                // Although I strongly recommend to have the tests in or a multiple of the mentioned time above
214                // waitTime = 0; I'll leave this block just for clarification
215            }
216            activeServices.put( Integer.valueOf( serviceId ), Integer.valueOf( waitTime ) );
217            LOG.logDebug( "service: ", service.getServiceName(), " will start in: ",
218                          activeServices.get( Integer.valueOf( serviceId ) ), " minutes" );
219    
220            if ( leastWaitTime == 0 || leastWaitTime > waitTime ) {
221                leastWaitTime = waitTime;
222                calLeastTime = Calendar.getInstance();
223            }
224    
225        }
226    
227        /**
228         * returns the service identified by its Id in the serviceList
229         *
230         * @param serviceId
231         * @return ServiceConfiguration
232         */
233        public ServiceConfiguration getService( int serviceId ) {
234            return services.get( Integer.valueOf( serviceId ) );
235        }
236    
237        /**
238         * removes the service identify by -id- from the ServiceList
239         *
240         * @param serviceId
241         * @return new ServiceConfiguration
242         *
243         */
244        public ServiceConfiguration removeService( int serviceId ) {
245            if ( activeServices.containsKey( Integer.valueOf( serviceId ) ) ) {
246                ServiceConfiguration service = services.get( Integer.valueOf( serviceId ) );
247                activeServices.remove( Integer.valueOf( serviceId ) );
248                serviceLogs.remove( service );
249                if ( service.getRefreshRate() == minTestInterval ) {
250                    minTestInterval = findMinTestInterval();
251                }
252                if ( minTestInterval == 0 ) {
253                    threadsuspended = true;
254                }
255            }
256    
257            return services.remove( serviceId );
258        }
259    
260        /**
261         * Finds the minimum test interval from the active Services
262         */
263        private int findMinTestInterval() {
264            int minInterval = 0;
265            Iterator<Integer> it = activeServices.keySet().iterator();
266            while ( it.hasNext() ) {
267                int serviceRefreshRate = services.get( it.next() ).getRefreshRate();
268                if ( minInterval == 0 || serviceRefreshRate < minInterval ) {
269                    minInterval = serviceRefreshRate;
270                }
271            }
272            return minInterval;
273        }
274    
275        /**
276         * This method will set the threadSuspended vaiable to true in order for the id-th service to stop sending
277         * GetCapabilities requests
278         *
279         * @param serviceId
280         *
281         */
282        public void stopServiceConfiguration( int serviceId ) {
283            activeServices.remove( serviceId );
284        }
285    
286        /**
287         * This method will set the threadSuspended vaiable to false in order for the id-th service to restart sending
288         * requests
289         *
290         * @param serviceId
291         *
292         */
293        public void startServiceConfiguration( int serviceId ) {
294            activeServices.put( serviceId, Integer.valueOf( 0 ) );
295        }
296    
297        /**
298         * @param serviceId
299         * @return true if the currrent Service id at all exists in watcher, false otherwise
300         */
301        public boolean containsService( int serviceId ) {
302            return services.containsKey( serviceId );
303        }
304    
305        /**
306         * returns list of services being watched
307         *
308         * @return the serviceList
309         */
310        public List<ServiceConfiguration> getServiceList() {
311            return new ArrayList<ServiceConfiguration>( services.values() );
312        }
313    
314        /**
315         * RefreshAllServices executes all the requests in the active monitors, and subtract the time from those who are
316         * waiting in line and restarts the time for those who had a turn This is the default to be called during the
317         * regular tests. If you want to execute tests in the main thread and without affecting the regular tests
318         * executeTest(int) and executeall()
319         */
320        public void refreshAllServices() {
321    
322            Set<Integer> keys = activeServices.keySet();
323            Iterator<Integer> it = keys.iterator();
324            while ( it.hasNext() ) {
325                Integer key = it.next();
326                int value = activeServices.get( key );
327                // Check will be repeated every minute
328                value -= 1;
329                ServiceConfiguration service = services.get( key );
330                // if the waiting time for this service has elapsed, and now its time to execute a new request
331                if ( value <= 0 ) {
332                    LOG.logDebug( "service: ", service.getServiceName(), " will be refreshed" );
333                    // resets the timing
334                    // -value adjusts the timing exactly to the subtracted sum. For example if a service refreshs
335                    // every 3 minutes and value is -1, that means this service has to wait 2 minutes this time not 3
336                    // activeServices.put( key, (Integer) service.getRefreshRate() - value );
337                    activeServices.put( key, (Integer) service.getRefreshRate() );
338                    ServiceLog serviceLog = serviceLogs.get( services.get( key ) );
339                    new ServiceInvoker( service, serviceLog ).executeTestThreaded();
340                } else {
341                    // else subtract the refresh rate
342                    activeServices.put( key, (Integer) value );
343                }
344            }
345        }
346    
347        /**
348         * executes the given service one time in the main thread
349         *
350         * @param serviceId
351         */
352        public void execute( int serviceId ) {
353    
354            ServiceConfiguration service = services.get( serviceId );
355            if ( service == null ) {
356                return;
357            }
358            ServiceLog serviceLog = this.serviceLogs.get( service );
359            new ServiceInvoker( service, serviceLog ).executeTest();
360        }
361    
362        /**
363         * This function is called on starting tomcat. It checks when was the last test performed before tomcat was
364         * shutdown. and sends an email to the user, notifying him with the situation and that a number of tests have been
365         * missed
366         *
367         * @param webinfPath
368         *            WEB-INF folder path
369         * @param conf
370         *            owsWatch configuration instance
371         */
372        public void compileDownTimeReport( String webinfPath, OwsWatchConfig conf ) {
373    
374            LOG.logDebug( "compileDownTimeReport()" );
375            String protFolderPath = StringTools.concat( 100, webinfPath, conf.getGeneral().getProtFolderPath() );
376            File protFolder = new File( protFolderPath );
377            if ( !protFolder.exists() || !protFolder.isDirectory() ) {
378                LOG.logError( Messages.getMessage( "ERROR_PROT_DIR_NOT_FOLDER", protFolderPath ) );
379                return;
380            }
381            Iterator<Integer> it = activeServices.keySet().iterator();
382            Hashtable<Integer, String> missedTests = new Hashtable<Integer, String>();
383    
384            while ( it.hasNext() ) {
385                Integer next = it.next();
386                LOG.logDebug( "Active test: ", next.intValue() );
387                File prot = getLatestProtocolPath( protFolderPath, next.intValue(), 2 );
388                if ( prot == null ) {
389                    LOG.logDebug( "protocol is null" );
390                    continue;
391                }
392                String protLatest = getLatestTest( prot );
393                if ( protLatest == null ) {
394                    LOG.logDebug( "protLastTest is null" );
395                    continue;
396                }
397                missedTests.put( next, protLatest );
398            }
399            LOG.logDebug( "number of missed tests: ", missedTests.keySet().size() );
400            writeAndSendTimeDownEmail( missedTests, conf );
401    
402        }
403    
404        /**
405         * Returns the latest written protocol file of a certain serviceconfiguration. The search starts with the actual
406         * date and tries to find the protocl of that date if not found it subtracts a month and repeat the operation. This
407         * procedure is repeated monthsDepth
408         *
409         * @param protFolderPath
410         *            Path to the folder containing the protocol files
411         * @param serviceID
412         *            ID to the serviceConfiguration
413         * @param monthsDepth
414         *            determines how many times this operation should be repeated, one month earlier every time
415         *
416         * @return Path to the latest protocol file or null if non is found
417         */
418        private File getLatestProtocolPath( String protFolderPath, int serviceID, int monthsDepth ) {
419    
420            Calendar now = Calendar.getInstance();
421            int month = now.get( Calendar.MONTH ) + 1;
422            int year = now.get( Calendar.YEAR );
423    
424            while ( monthsDepth-- > 0 ) {
425    
426                String path = StringTools.concat( 200, protFolderPath, "/", "protocol_ID_", serviceID, "_", year, "_",
427                                                  month, ".xml" );
428                LOG.logDebug( "path to protocol: ", path );
429                File prot = new File( path );
430                if ( prot.exists() ) {
431                    return prot;
432                }
433                if ( --month == 0 ) {
434                    month = 12;
435                    year--;
436                }
437            }
438            return null;
439        }
440    
441        /**
442         * Parses the given xml file and finds the latest test executed on a ServiceConfiguration
443         *
444         * @param protFile
445         *            protocol path of a given ServiceConfiguration
446         * @return Date of the latest test executed. Note. Year and month
447         */
448        private String getLatestTest( File protFile ) {
449    
450            try {
451                Document doc = XMLTools.parse( new FileReader( protFile ) );
452                Element elem = doc.getDocumentElement();
453                List<String> nodes = XMLTools.getNodesAsStringList( elem, "./Entry/TimeStamp",
454                                                                    CommonNamepspaces.getNameSpaceContext() );
455                return nodes.get( nodes.size() - 1 );
456    
457            } catch ( Exception e ) {
458                LOG.logError( e.getMessage(), e );
459            }
460            return null;
461        }
462    
463        /**
464         * Authors the down time report and sends it to the designated users
465         *
466         * @param missedTests
467         *            a hastable containing the missed tests
468         * @param conf
469         *            owsWatchConfiguration
470         */
471        protected void writeAndSendTimeDownEmail( Hashtable<Integer, String> missedTests, OwsWatchConfig conf ) {
472    
473            String message = createDownTimeReportBody( missedTests );
474            String subject = Messages.getString( "TimeDownReport.subject" );
475            List<String> emails = getUserEmails( conf );
476            String mailFrom = conf.getGeneral().getMailFrom();
477            String mailServer = conf.getGeneral().getMailServer();
478            Iterator<String> email = emails.iterator();
479            while ( email.hasNext() ) {
480                // send message to the user
481                EMailMessage mm = new EMailMessage( mailFrom, email.next(), subject, message );
482                try {
483                    mm.setMimeType( MailMessage.PLAIN_TEXT );
484                    MailHelper.createAndSendMail( mm, mailServer );
485                } catch ( Exception e ) {
486                    LOG.logError( e.getMessage() );
487                }
488    
489            }
490    
491        }
492    
493        /**
494         * Create the content of the down time time report
495         *
496         * @param missedTests
497         * @return the down time report
498         */
499        protected String createDownTimeReportBody( Hashtable<Integer, String> missedTests ) {
500            StringBuffer body = new StringBuffer( 500 );
501            body.append( Messages.getString( "TimeDownReport.address" ) );
502            body.append( Messages.getMessage( "TimeDownReport.messageBody", Calendar.getInstance().getTime().toString() ) );
503            Iterator<Integer> it = missedTests.keySet().iterator();
504            while ( it.hasNext() ) {
505                Integer next = it.next();
506                String name = services.get( next ).getServiceName();
507                String lastTest = missedTests.get( next );
508                body.append( Messages.getMessage( "TimeDownReport.missedTest", name, lastTest ) );
509            }
510            body.append( Messages.getString( "TimeDownReport.closingWords" ) );
511            body.append( Messages.getString( "TimeDownReport.signer" ) );
512            return body.toString();
513        }
514    
515        private List<String> getUserEmails( OwsWatchConfig conf ) {
516            List<String> emails = new ArrayList<String>();
517            Map<String, User> users = conf.getGeneral().getUsers();
518            Iterator<User> user = users.values().iterator();
519            while ( user.hasNext() ) {
520                emails.add( user.next().getEmail() );
521            }
522            return emails;
523        }
524    
525        /**
526         * @return A map of all services
527         */
528        public Map<Integer, ServiceConfiguration> getServices() {
529            return services;
530        }
531    
532        /**
533         * @return Map<Integer, ServiceLog> key = ServiceConfiguration id
534         */
535        public Map<ServiceConfiguration, ServiceLog> getServiceLogs() {
536            return serviceLogs;
537        }
538    
539        /**
540         * @return true if thread is suspended, false otherwise
541         */
542        public boolean isThreadsuspended() {
543            return threadsuspended;
544        }
545    
546        /**
547         * @param threadsuspended
548         */
549        public void setThreadsuspended( boolean threadsuspended ) {
550            this.threadsuspended = threadsuspended;
551        }
552    }