001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }