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 }