View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.service.daemon;
35  
36  import fr.paris.lutece.portal.service.datastore.DatastoreService;
37  import fr.paris.lutece.portal.service.init.LuteceInitException;
38  import fr.paris.lutece.portal.service.util.AppLogService;
39  import fr.paris.lutece.portal.service.util.AppPropertiesService;
40  
41  import java.util.Collection;
42  import java.util.HashMap;
43  import java.util.Map;
44  import java.util.Random;
45  import java.util.concurrent.ConcurrentHashMap;
46  import java.util.concurrent.Executors;
47  import java.util.concurrent.ScheduledFuture;
48  import java.util.concurrent.ScheduledThreadPoolExecutor;
49  import java.util.concurrent.TimeUnit;
50  
51  
52  /**
53   *  this class provides methods to manage daemons services
54   * */
55  public final class AppDaemonService
56  {
57      private static final String PROPERTY_MAX_INITIAL_START_DELAY = "daemon.maxInitialStartDelay";
58      private static final String PROPERTY_MAX_AWAIT_TERMINATION_DELAY = "daemon.maxAwaitTerminationDelay";
59      private static final String PROPERTY_SCHEDULED_THREAD_CORE_POOL_SIZE = "daemon.ScheduledThreadCorePoolSize";
60      private static final String PROPERTY_DAEMON_ON_STARTUP = ".onStartUp";
61      private static final String PROPERTY_DAEMON_INTERVAL = ".interval";
62      private static final String KEY_DAEMON_PREFIX = "core.daemon.";
63      private static final int MAX_INITIAL_START_DELAY = AppPropertiesService.getPropertyInt( PROPERTY_MAX_INITIAL_START_DELAY,
64              30 );
65      private static final int MAX_AWAIT_TERMINATION_DELAY = AppPropertiesService.getPropertyInt( PROPERTY_MAX_AWAIT_TERMINATION_DELAY,
66              15 );
67      private static final int DAEMON_CORE_POOL_SIZE = AppPropertiesService.getPropertyInt( PROPERTY_SCHEDULED_THREAD_CORE_POOL_SIZE,
68              30 );
69      private static final Map<String, DaemonEntry> _mapDaemonEntries = new HashMap<String, DaemonEntry>(  );
70      private static final ScheduledThreadPoolExecutor _scheduler = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool( DAEMON_CORE_POOL_SIZE,
71              new DaemonThreadFactory(  ) );
72      private static ConcurrentHashMap<String, ScheduledFuture<?>> _lRunningThread = new ConcurrentHashMap<String, ScheduledFuture<?>>(  );
73      private static final Random _random = new Random(  );
74      private static boolean _bInit;
75  
76      /** Private constructor */
77      private AppDaemonService(  )
78      {
79      }
80  
81      /**
82       * Performs initialization of the DaemonFactory.  Note that this should
83       * return right away so that processing can continue (IE thread off
84       * everything)
85       * @throws LuteceInitException If an error occurred
86       */
87      public static synchronized void init(  ) throws LuteceInitException
88      {
89          // already initialized
90          if ( _bInit )
91          {
92              return;
93          }
94  
95          if ( _mapDaemonEntries.size(  ) > 0 )
96          {
97              // Unsynchronized daemon start
98              int nInitialDaemon = 0;
99  
100             for ( DaemonEntry entry : _mapDaemonEntries.values(  ) )
101             {
102                 if ( entry.onStartup(  ) )
103                 {
104                     nInitialDaemon++;
105                 }
106             }
107 
108             int nDelay = MAX_INITIAL_START_DELAY;
109 
110             if ( nInitialDaemon > 0 )
111             {
112                 nDelay = MAX_INITIAL_START_DELAY / nInitialDaemon;
113             }
114 
115             int nInitialDelay = 0;
116 
117             // Register daemons
118             for ( DaemonEntry entry : _mapDaemonEntries.values(  ) )
119             {
120                 // starts any daemon declared as startup daemons
121                 if ( entry.onStartup(  ) )
122                 {
123                     nInitialDelay += nDelay;
124 
125                     scheduleThread( entry, nInitialDelay );
126                 }
127             }
128         }
129 
130         _bInit = true;
131     }
132 
133     /**
134      * Register a daemon by its entry
135      * @param entry The daemon entry
136      * @throws LuteceInitException If an error occurred
137      */
138     public static void registerDaemon( DaemonEntry entry )
139         throws LuteceInitException
140     {
141         String strIntervalKey = getIntervalKey( entry.getId(  ) );
142         String strIntervalKeyDefaultValue = null;
143 
144         //init interval value if no exists
145         if ( !DatastoreService.existsInstanceKey( strIntervalKey ) )
146         {
147             strIntervalKeyDefaultValue = AppPropertiesService.getProperty( "daemon." + entry.getId(  ) + ".interval",
148                     "10" );
149             DatastoreService.setInstanceDataValue( strIntervalKey, strIntervalKeyDefaultValue );
150         }
151 
152         String strIntervalKeyValue = DatastoreService.getInstanceDataValue( strIntervalKey, strIntervalKeyDefaultValue );
153 
154         long lInterval = Long.valueOf( strIntervalKeyValue );
155 
156         String strOnStartupKey = getOnStartupKey( entry.getId(  ) );
157         String strOnStartupDefaultValue = null;
158 
159         //init onStartup value if no exists
160         if ( !DatastoreService.existsInstanceKey( strOnStartupKey ) )
161         {
162             strOnStartupDefaultValue = AppPropertiesService.getProperty( "daemon." + entry.getId(  ) + ".onstartup", "0" )
163                                                            .equals( "1" ) ? DatastoreService.VALUE_TRUE
164                                                                           : DatastoreService.VALUE_FALSE;
165             DatastoreService.setInstanceDataValue( strOnStartupKey, strOnStartupDefaultValue );
166         }
167 
168         String strOnStarupvalue = DatastoreService.getInstanceDataValue( strOnStartupKey, strOnStartupDefaultValue );
169         boolean bOnStartup = Boolean.valueOf( strOnStarupvalue );
170 
171         entry.setInterval( lInterval );
172         entry.setOnStartUp( bOnStartup );
173 
174         try
175         {
176             entry.loadDaemon(  );
177         }
178         catch ( ClassNotFoundException e )
179         {
180             throw new LuteceInitException( "Couldn't instantiate daemon: " + entry.getId(  ), e );
181         }
182         catch ( InstantiationException e )
183         {
184             throw new LuteceInitException( "Couldn't instantiate daemon: " + entry.getId(  ), e );
185         }
186         catch ( IllegalAccessException e )
187         {
188             throw new LuteceInitException( "Couldn't instantiate daemon: " + entry.getId(  ), e );
189         }
190 
191         // Add plugin name to Daemon class
192         if ( entry.getPluginName(  ) != null )
193         {
194             entry.getDaemon(  ).setPluginName( entry.getPluginName(  ) );
195         }
196 
197         _mapDaemonEntries.put( entry.getId(  ), entry );
198 
199         AppLogService.info( "New Daemon registered : " + entry.getId(  ) );
200     }
201 
202     /**
203      * Unregister a daemon
204      * @param strDaemonKey The daemon key
205      */
206     public static void unregisterDaemon( String strDaemonKey )
207     {
208         unScheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
209         _mapDaemonEntries.remove( strDaemonKey );
210     }
211 
212     /**
213      * Starts a daemon
214      * @param strDaemonKey The daemon key
215      */
216     public static void startDaemon( String strDaemonKey )
217     {
218         scheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
219     }
220 
221     /**
222      * Stops a daemon
223      * @param strDaemonKey The daemon key
224      */
225     public static void stopDaemon( String strDaemonKey )
226     {
227         unScheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
228     }
229 
230     /**
231      * modify daemon interval
232      * @param strDaemonKey The daemon key
233      * @param strDaemonInterval the daemon interval
234      */
235     public static void modifyDaemonInterval( String strDaemonKey, String strDaemonInterval )
236     {
237         DaemonEntry entry = _mapDaemonEntries.get( strDaemonKey );
238 
239         if ( entry != null )
240         {
241             entry.setInterval( new Long( strDaemonInterval ) );
242             DatastoreService.setInstanceDataValue( getIntervalKey( entry.getId(  ) ), strDaemonInterval );
243         }
244     }
245 
246     /**
247      * Add daemon to schedule's queue
248      * @param entry The DaemonEntry
249      */
250     private static void scheduleThread( DaemonEntry entry )
251     {
252         scheduleThread( entry, _random.nextInt( MAX_INITIAL_START_DELAY ) );
253     }
254 
255     /**
256      *  Add daemon to schedule's queue
257      * @param entry The DaemonEntry
258      * @param nInitialDelay Initial start delay
259      */
260     private static void scheduleThread( DaemonEntry entry, int nInitialDelay )
261     {
262         ScheduledFuture<?> result = _lRunningThread.get( entry.getId(  ) );
263 
264         if ( result == null )
265         {
266             ScheduledFuture<?> task = _scheduler.scheduleAtFixedRate( entry.getDaemonThread(  ), nInitialDelay,
267                     entry.getInterval(  ), TimeUnit.SECONDS );
268 
269             _lRunningThread.putIfAbsent( entry.getId(  ), task );
270             AppLogService.info( "Starting daemon '" + entry.getId(  ) + "'" );
271         }
272 
273         entry.setIsRunning( true );
274         //update onStartup property
275         DatastoreService.setInstanceDataValue( getOnStartupKey( entry.getId(  ) ), DatastoreService.VALUE_TRUE );
276     }
277 
278     /**
279      * Remove daemon from schedule's queue
280      * @param entry The DaemonEntry
281      */
282     private static void unScheduleThread( DaemonEntry entry )
283     {
284         cancelScheduledThread( entry.getId(  ) );
285         entry.setIsRunning( false );
286         //update onStartup property
287         DatastoreService.setInstanceDataValue( getOnStartupKey( entry.getId(  ) ), DatastoreService.VALUE_FALSE );
288         AppLogService.info( "Stopping daemon '" + entry.getId(  ) + "'" );
289     }
290 
291     /**
292      * Cancel scheduled thread (don't interrupt if it is running )
293      * @param strEntryId The DaemonEntry Id
294      */
295     protected static void cancelScheduledThread( String strEntryId )
296     {
297         ScheduledFuture<?> task = _lRunningThread.get( strEntryId );
298 
299         if ( task != null )
300         {
301             task.cancel( false );
302             _scheduler.remove( (Runnable) task );
303             _scheduler.purge(  );
304             _lRunningThread.remove( strEntryId );
305         }
306     }
307 
308     /**
309      * Get the current known DaemonEntries within the DaemonFactory
310      *
311      * @return the entries list of daemons declaration
312      */
313     public static Collection<DaemonEntry> getDaemonEntries(  )
314     {
315         return _mapDaemonEntries.values(  );
316     }
317 
318     /**
319      * Performs the shutdown of the DaemonFactory.
320      */
321     public static void shutdown(  )
322     {
323         AppLogService.info( 
324             "Lutece daemons scheduler stop requested : trying to terminate gracefully daemons list (max wait " +
325             MAX_AWAIT_TERMINATION_DELAY + " s)." );
326         _scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy( false );
327         _scheduler.setContinueExistingPeriodicTasksAfterShutdownPolicy( false );
328         _scheduler.shutdown(  );
329 
330         try
331         {
332             if ( _scheduler.awaitTermination( MAX_AWAIT_TERMINATION_DELAY, TimeUnit.SECONDS ) )
333             {
334                 AppLogService.info( "All daemons shutdown successfully." );
335             }
336             else
337             {
338                 AppLogService.info( _scheduler.getActiveCount(  ) +
339                     " daemons still running, trying to interrupt them..." );
340                 _scheduler.shutdownNow(  );
341 
342                 if ( _scheduler.awaitTermination( 1, TimeUnit.SECONDS ) )
343                 {
344                     AppLogService.info( "All running daemons successfully interrupted." );
345                 }
346                 else
347                 {
348                     AppLogService.error( "Interrupt failed : " + _scheduler.getActiveCount(  ) +
349                         " daemons still running." );
350                 }
351             }
352         }
353         catch ( InterruptedException e )
354         {
355             AppLogService.error( "Error during waiting for daemons termination", e );
356         }
357         finally
358         {
359             _scheduler.purge(  );
360         }
361     }
362 
363     /**
364      * Gets a daemon object from its key name
365      *
366      * @param strDaemonKey The daemon key
367      * @return The daemon
368      */
369     public static Daemon getDaemon( String strDaemonKey )
370     {
371         DaemonEntry entry = _mapDaemonEntries.get( strDaemonKey );
372 
373         return entry.getDaemon(  );
374     }
375 
376     /**
377      * return the OnStartup key link to the daemon
378      * @param strDaemonKey The daemon key
379      * @return The key
380      */
381     private static String getOnStartupKey( String strDaemonKey )
382     {
383         return KEY_DAEMON_PREFIX + strDaemonKey + PROPERTY_DAEMON_ON_STARTUP;
384     }
385 
386     /**
387      * return the Interval key link to the daemon
388      * @param strDaemonKey The daemon key
389      * @return The key
390      */
391     private static String getIntervalKey( String strDaemonKey )
392     {
393         return KEY_DAEMON_PREFIX + strDaemonKey + PROPERTY_DAEMON_INTERVAL;
394     }
395 }