View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of 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 java.util.Collection;
37  import java.util.HashMap;
38  import java.util.Map;
39  import java.util.Random;
40  import java.util.concurrent.TimeUnit;
41  
42  import fr.paris.lutece.portal.service.datastore.DatastoreService;
43  import fr.paris.lutece.portal.service.init.LuteceInitException;
44  import fr.paris.lutece.portal.service.spring.SpringContextService;
45  import fr.paris.lutece.portal.service.util.AppLogService;
46  import fr.paris.lutece.portal.service.util.AppPropertiesService;
47  
48  /**
49   * this class provides methods to manage daemons services
50   */
51  public final class AppDaemonService
52  {
53      private static final String PROPERTY_MAX_INITIAL_START_DELAY = "daemon.maxInitialStartDelay";
54      private static final String PROPERTY_DAEMON_ON_STARTUP = ".onStartUp";
55      private static final String PROPERTY_DAEMON_INTERVAL = ".interval";
56      private static final String KEY_DAEMON = "daemon.";
57      private static final String KEY_DAEMON_PREFIX = "core." + KEY_DAEMON;
58      private static final Map<String, DaemonEntry> _mapDaemonEntries = new HashMap<>( );
59      private static final Random _random = new Random( );
60      private static boolean _bInit;
61      private static IDaemonScheduler _executor;
62  
63      /** Private constructor */
64      private AppDaemonService( )
65      {
66      }
67  
68      /**
69       * Performs initialization of the DaemonFactory. Note that this should return right away so that processing can continue (IE thread off everything)
70       * 
71       * @throws LuteceInitException
72       *             If an error occurred
73       */
74      public static synchronized void init( )
75      {
76          // already initialized
77          if ( _bInit )
78          {
79              return;
80          }
81  
82          _executor = SpringContextService.getBean( IDaemonScheduler.BEAN_NAME );
83  
84          if ( _mapDaemonEntries.size( ) > 0 )
85          {
86              // Unsynchronized daemon start
87              int nInitialDaemon = 0;
88  
89              for ( DaemonEntry entry : _mapDaemonEntries.values( ) )
90              {
91                  if ( entry.onStartup( ) )
92                  {
93                      nInitialDaemon++;
94                  }
95              }
96  
97              int nDelay = AppPropertiesService.getPropertyInt( PROPERTY_MAX_INITIAL_START_DELAY, 30 );
98  
99              if ( nInitialDaemon > 0 )
100             {
101                 nDelay = nDelay / nInitialDaemon;
102             }
103 
104             int nInitialDelay = 0;
105 
106             // Register daemons
107             for ( DaemonEntry entry : _mapDaemonEntries.values( ) )
108             {
109                 // starts any daemon declared as startup daemons
110                 if ( entry.onStartup( ) )
111                 {
112                     nInitialDelay += nDelay;
113 
114                     scheduleThread( entry, nInitialDelay );
115                 }
116             }
117         }
118 
119         _bInit = true;
120     }
121 
122     /**
123      * Register a daemon by its entry
124      * 
125      * @param entry
126      *            The daemon entry
127      * @throws LuteceInitException
128      *             If an error occurred
129      */
130     public static void registerDaemon( DaemonEntry entry ) throws LuteceInitException
131     {
132         if ( _mapDaemonEntries.containsKey( entry.getId( ) ) )
133         {
134             AppLogService.error( "Ignoring attempt to register already registered daemon {}", entry.getId( ) );
135             return;
136         }
137         String strIntervalKey = getIntervalKey( entry.getId( ) );
138         String strIntervalKeyDefaultValue = null;
139 
140         // init interval value if no exists
141         if ( !DatastoreService.existsInstanceKey( strIntervalKey ) )
142         {
143             strIntervalKeyDefaultValue = AppPropertiesService.getProperty( KEY_DAEMON + entry.getId( ) + PROPERTY_DAEMON_INTERVAL, "10" );
144             DatastoreService.setInstanceDataValue( strIntervalKey, strIntervalKeyDefaultValue );
145         }
146 
147         String strIntervalKeyValue = DatastoreService.getInstanceDataValue( strIntervalKey, strIntervalKeyDefaultValue );
148 
149         long lInterval = Long.parseLong( strIntervalKeyValue );
150 
151         String strOnStartupKey = getOnStartupKey( entry.getId( ) );
152         String strOnStartupDefaultValue = null;
153 
154         // init onStartup value if no exists
155         if ( !DatastoreService.existsInstanceKey( strOnStartupKey ) )
156         {
157             strOnStartupDefaultValue = AppPropertiesService.getProperty( KEY_DAEMON + entry.getId( ) + ".onstartup", "0" ).equals( "1" )
158                     ? DatastoreService.VALUE_TRUE
159                     : DatastoreService.VALUE_FALSE;
160             DatastoreService.setInstanceDataValue( strOnStartupKey, strOnStartupDefaultValue );
161         }
162 
163         String strOnStarupvalue = DatastoreService.getInstanceDataValue( strOnStartupKey, strOnStartupDefaultValue );
164         boolean bOnStartup = Boolean.parseBoolean( strOnStarupvalue );
165 
166         entry.setInterval( lInterval );
167         entry.setOnStartUp( bOnStartup );
168 
169         try
170         {
171             entry.loadDaemon( );
172         }
173         catch( IllegalAccessException | InstantiationException | ClassNotFoundException e )
174         {
175             throw new LuteceInitException( "Couldn't instantiate daemon: " + entry.getId( ), e );
176         }
177 
178         // Add plugin name to Daemon class
179         if ( entry.getPluginName( ) != null )
180         {
181             entry.getDaemon( ).setPluginName( entry.getPluginName( ) );
182         }
183 
184         _mapDaemonEntries.put( entry.getId( ), entry );
185 
186         AppLogService.info( "New Daemon registered : {} ", entry.getId( ) );
187     }
188 
189     /**
190      * Unregister a daemon
191      * 
192      * @param strDaemonKey
193      *            The daemon key
194      */
195     public static void unregisterDaemon( String strDaemonKey )
196     {
197         unScheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
198         _mapDaemonEntries.remove( strDaemonKey );
199     }
200 
201     /**
202      * Starts a daemon
203      * 
204      * @param strDaemonKey
205      *            The daemon key
206      */
207     public static void startDaemon( String strDaemonKey )
208     {
209         scheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
210     }
211 
212     /**
213      * Stops a daemon
214      * 
215      * @param strDaemonKey
216      *            The daemon key
217      */
218     public static void stopDaemon( String strDaemonKey )
219     {
220         unScheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
221     }
222 
223     /**
224      * Signal a daemon for execution in the immediate future.
225      * <p>
226      * This can fail is resources are limited, which should be exceptional.
227      *
228      * @param strDaemonKey
229      *            the daemon key
230      * @return <code>true</code> if the daemon was successfully signaled, <code>false</code> otherwise
231      * @since 6.0.0
232      */
233     public static boolean signalDaemon( String strDaemonKey )
234     {
235         return signalDaemon( strDaemonKey, 0L, TimeUnit.MILLISECONDS );
236     }
237 
238     /**
239      * Signal a daemon for execution in the immediate future.
240      * <p>
241      * This can fail is resources are limited, which should be exceptional.
242      *
243      * @param strDaemonKey
244      *            the daemon key
245      * @param nDelay
246      *            the delay before execution
247      * @param unit
248      *            the unit of <code>nDelay</code> argument
249      * @return <code>true</code> if the daemon was successfully signaled, <code>false</code> otherwise
250      * @since 6.0.0
251      */
252     public static boolean signalDaemon( String strDaemonKey, long nDelay, TimeUnit unit )
253     {
254         return _executor.enqueue( _mapDaemonEntries.get( strDaemonKey ), nDelay, unit );
255     }
256 
257     /**
258      * modify daemon interval
259      * 
260      * @param strDaemonKey
261      *            The daemon key
262      * @param strDaemonInterval
263      *            the daemon interval
264      */
265     public static void modifyDaemonInterval( String strDaemonKey, String strDaemonInterval )
266     {
267         DaemonEntry entry = _mapDaemonEntries.get( strDaemonKey );
268 
269         if ( entry != null )
270         {
271             entry.setInterval( Long.valueOf( strDaemonInterval ) );
272             DatastoreService.setInstanceDataValue( getIntervalKey( entry.getId( ) ), strDaemonInterval );
273         }
274     }
275 
276     /**
277      * Add daemon to schedule's queue
278      * 
279      * @param entry
280      *            The DaemonEntry
281      */
282     private static void scheduleThread( DaemonEntry entry )
283     {
284         scheduleThread( entry, _random.nextInt( AppPropertiesService.getPropertyInt( PROPERTY_MAX_INITIAL_START_DELAY, 30 ) ) );
285     }
286 
287     /**
288      * Add daemon to schedule's queue
289      * 
290      * @param entry
291      *            The DaemonEntry
292      * @param nInitialDelay
293      *            Initial start delay
294      */
295     private static void scheduleThread( DaemonEntry entry, int nInitialDelay )
296     {
297         AppLogService.info( "Scheduling daemon {} ; first run in {} seconds", entry.getId( ), nInitialDelay );
298         entry.setIsRunning( true );
299         _executor.schedule( entry, nInitialDelay, TimeUnit.SECONDS );
300         // update onStartup property
301         DatastoreService.setInstanceDataValue( getOnStartupKey( entry.getId( ) ), DatastoreService.VALUE_TRUE );
302     }
303 
304     /**
305      * Remove daemon from schedule's queue
306      * 
307      * @param entry
308      *            The DaemonEntry
309      */
310     private static void unScheduleThread( DaemonEntry entry )
311     {
312         cancelScheduledThread( entry.getId( ) );
313         entry.setIsRunning( false );
314         // update onStartup property
315         DatastoreService.setInstanceDataValue( getOnStartupKey( entry.getId( ) ), DatastoreService.VALUE_FALSE );
316         AppLogService.info( "Stopping daemon '{}'", entry.getId( ) );
317     }
318 
319     /**
320      * Cancel scheduled thread (don't interrupt if it is running )
321      * 
322      * @param strEntryId
323      *            The DaemonEntry Id
324      */
325     protected static void cancelScheduledThread( String strEntryId )
326     {
327         _executor.unSchedule( _mapDaemonEntries.get( strEntryId ) );
328     }
329 
330     /**
331      * Get the current known DaemonEntries within the DaemonFactory
332      *
333      * @return the entries list of daemons declaration
334      */
335     public static Collection<DaemonEntry> getDaemonEntries( )
336     {
337         return _mapDaemonEntries.values( );
338     }
339 
340     /**
341      * Performs the shutdown of the DaemonFactory.
342      */
343     public static void shutdown( )
344     {
345         _executor.shutdown( );
346     }
347 
348     /**
349      * Gets a daemon object from its key name
350      *
351      * @param strDaemonKey
352      *            The daemon key
353      * @return The daemon
354      */
355     public static Daemon getDaemon( String strDaemonKey )
356     {
357         DaemonEntry entry = _mapDaemonEntries.get( strDaemonKey );
358 
359         return entry.getDaemon( );
360     }
361 
362     /**
363      * return the OnStartup key link to the daemon
364      * 
365      * @param strDaemonKey
366      *            The daemon key
367      * @return The key
368      */
369     private static String getOnStartupKey( String strDaemonKey )
370     {
371         return KEY_DAEMON_PREFIX + strDaemonKey + PROPERTY_DAEMON_ON_STARTUP;
372     }
373 
374     /**
375      * return the Interval key link to the daemon
376      * 
377      * @param strDaemonKey
378      *            The daemon key
379      * @return The key
380      */
381     private static String getIntervalKey( String strDaemonKey )
382     {
383         return KEY_DAEMON_PREFIX + strDaemonKey + PROPERTY_DAEMON_INTERVAL;
384     }
385 }