AppDaemonService.java
/*
* Copyright (c) 2002-2022, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.portal.service.daemon;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import fr.paris.lutece.portal.service.datastore.DatastoreService;
import fr.paris.lutece.portal.service.init.LuteceInitException;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
/**
* this class provides methods to manage daemons services
*/
public final class AppDaemonService
{
private static final String PROPERTY_MAX_INITIAL_START_DELAY = "daemon.maxInitialStartDelay";
private static final String PROPERTY_DAEMON_ON_STARTUP = ".onStartUp";
private static final String PROPERTY_DAEMON_INTERVAL = ".interval";
private static final String KEY_DAEMON = "daemon.";
private static final String KEY_DAEMON_PREFIX = "core." + KEY_DAEMON;
private static final Map<String, DaemonEntry> _mapDaemonEntries = new HashMap<>( );
private static final Random _random = new Random( );
private static boolean _bInit;
private static IDaemonScheduler _executor;
/** Private constructor */
private AppDaemonService( )
{
}
/**
* Performs initialization of the DaemonFactory. Note that this should return right away so that processing can continue (IE thread off everything)
*
* @throws LuteceInitException
* If an error occurred
*/
public static synchronized void init( )
{
// already initialized
if ( _bInit )
{
return;
}
_executor = SpringContextService.getBean( IDaemonScheduler.BEAN_NAME );
if ( _mapDaemonEntries.size( ) > 0 )
{
// Unsynchronized daemon start
int nInitialDaemon = 0;
for ( DaemonEntry entry : _mapDaemonEntries.values( ) )
{
if ( entry.onStartup( ) )
{
nInitialDaemon++;
}
}
int nDelay = AppPropertiesService.getPropertyInt( PROPERTY_MAX_INITIAL_START_DELAY, 30 );
if ( nInitialDaemon > 0 )
{
nDelay = nDelay / nInitialDaemon;
}
int nInitialDelay = 0;
// Register daemons
for ( DaemonEntry entry : _mapDaemonEntries.values( ) )
{
// starts any daemon declared as startup daemons
if ( entry.onStartup( ) )
{
nInitialDelay += nDelay;
scheduleThread( entry, nInitialDelay );
}
}
}
_bInit = true;
}
/**
* Register a daemon by its entry
*
* @param entry
* The daemon entry
* @throws LuteceInitException
* If an error occurred
*/
public static void registerDaemon( DaemonEntry entry ) throws LuteceInitException
{
if ( _mapDaemonEntries.containsKey( entry.getId( ) ) )
{
AppLogService.error( "Ignoring attempt to register already registered daemon {}", entry.getId( ) );
return;
}
String strIntervalKey = getIntervalKey( entry.getId( ) );
String strIntervalKeyDefaultValue = null;
// init interval value if no exists
if ( !DatastoreService.existsInstanceKey( strIntervalKey ) )
{
strIntervalKeyDefaultValue = AppPropertiesService.getProperty( KEY_DAEMON + entry.getId( ) + PROPERTY_DAEMON_INTERVAL, "10" );
DatastoreService.setInstanceDataValue( strIntervalKey, strIntervalKeyDefaultValue );
}
String strIntervalKeyValue = DatastoreService.getInstanceDataValue( strIntervalKey, strIntervalKeyDefaultValue );
long lInterval = Long.parseLong( strIntervalKeyValue );
String strOnStartupKey = getOnStartupKey( entry.getId( ) );
String strOnStartupDefaultValue = null;
// init onStartup value if no exists
if ( !DatastoreService.existsInstanceKey( strOnStartupKey ) )
{
strOnStartupDefaultValue = AppPropertiesService.getProperty( KEY_DAEMON + entry.getId( ) + ".onstartup", "0" ).equals( "1" )
? DatastoreService.VALUE_TRUE
: DatastoreService.VALUE_FALSE;
DatastoreService.setInstanceDataValue( strOnStartupKey, strOnStartupDefaultValue );
}
String strOnStarupvalue = DatastoreService.getInstanceDataValue( strOnStartupKey, strOnStartupDefaultValue );
boolean bOnStartup = Boolean.parseBoolean( strOnStarupvalue );
entry.setInterval( lInterval );
entry.setOnStartUp( bOnStartup );
try
{
entry.loadDaemon( );
}
catch( IllegalAccessException | InstantiationException | ClassNotFoundException e )
{
throw new LuteceInitException( "Couldn't instantiate daemon: " + entry.getId( ), e );
}
// Add plugin name to Daemon class
if ( entry.getPluginName( ) != null )
{
entry.getDaemon( ).setPluginName( entry.getPluginName( ) );
}
_mapDaemonEntries.put( entry.getId( ), entry );
AppLogService.info( "New Daemon registered : {} ", entry.getId( ) );
}
/**
* Unregister a daemon
*
* @param strDaemonKey
* The daemon key
*/
public static void unregisterDaemon( String strDaemonKey )
{
unScheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
_mapDaemonEntries.remove( strDaemonKey );
}
/**
* Starts a daemon
*
* @param strDaemonKey
* The daemon key
*/
public static void startDaemon( String strDaemonKey )
{
scheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
}
/**
* Stops a daemon
*
* @param strDaemonKey
* The daemon key
*/
public static void stopDaemon( String strDaemonKey )
{
unScheduleThread( _mapDaemonEntries.get( strDaemonKey ) );
}
/**
* Signal a daemon for execution in the immediate future.
* <p>
* This can fail is resources are limited, which should be exceptional.
*
* @param strDaemonKey
* the daemon key
* @return <code>true</code> if the daemon was successfully signaled, <code>false</code> otherwise
* @since 6.0.0
*/
public static boolean signalDaemon( String strDaemonKey )
{
return signalDaemon( strDaemonKey, 0L, TimeUnit.MILLISECONDS );
}
/**
* Signal a daemon for execution in the immediate future.
* <p>
* This can fail is resources are limited, which should be exceptional.
*
* @param strDaemonKey
* the daemon key
* @param nDelay
* the delay before execution
* @param unit
* the unit of <code>nDelay</code> argument
* @return <code>true</code> if the daemon was successfully signaled, <code>false</code> otherwise
* @since 6.0.0
*/
public static boolean signalDaemon( String strDaemonKey, long nDelay, TimeUnit unit )
{
return _executor.enqueue( _mapDaemonEntries.get( strDaemonKey ), nDelay, unit );
}
/**
* modify daemon interval
*
* @param strDaemonKey
* The daemon key
* @param strDaemonInterval
* the daemon interval
*/
public static void modifyDaemonInterval( String strDaemonKey, String strDaemonInterval )
{
DaemonEntry entry = _mapDaemonEntries.get( strDaemonKey );
if ( entry != null )
{
entry.setInterval( Long.valueOf( strDaemonInterval ) );
DatastoreService.setInstanceDataValue( getIntervalKey( entry.getId( ) ), strDaemonInterval );
}
}
/**
* Add daemon to schedule's queue
*
* @param entry
* The DaemonEntry
*/
private static void scheduleThread( DaemonEntry entry )
{
scheduleThread( entry, _random.nextInt( AppPropertiesService.getPropertyInt( PROPERTY_MAX_INITIAL_START_DELAY, 30 ) ) );
}
/**
* Add daemon to schedule's queue
*
* @param entry
* The DaemonEntry
* @param nInitialDelay
* Initial start delay
*/
private static void scheduleThread( DaemonEntry entry, int nInitialDelay )
{
AppLogService.info( "Scheduling daemon {} ; first run in {} seconds", entry.getId( ), nInitialDelay );
entry.setIsRunning( true );
_executor.schedule( entry, nInitialDelay, TimeUnit.SECONDS );
// update onStartup property
DatastoreService.setInstanceDataValue( getOnStartupKey( entry.getId( ) ), DatastoreService.VALUE_TRUE );
}
/**
* Remove daemon from schedule's queue
*
* @param entry
* The DaemonEntry
*/
private static void unScheduleThread( DaemonEntry entry )
{
cancelScheduledThread( entry.getId( ) );
entry.setIsRunning( false );
// update onStartup property
DatastoreService.setInstanceDataValue( getOnStartupKey( entry.getId( ) ), DatastoreService.VALUE_FALSE );
AppLogService.info( "Stopping daemon '{}'", entry.getId( ) );
}
/**
* Cancel scheduled thread (don't interrupt if it is running )
*
* @param strEntryId
* The DaemonEntry Id
*/
protected static void cancelScheduledThread( String strEntryId )
{
_executor.unSchedule( _mapDaemonEntries.get( strEntryId ) );
}
/**
* Get the current known DaemonEntries within the DaemonFactory
*
* @return the entries list of daemons declaration
*/
public static Collection<DaemonEntry> getDaemonEntries( )
{
return _mapDaemonEntries.values( );
}
/**
* Performs the shutdown of the DaemonFactory.
*/
public static void shutdown( )
{
_executor.shutdown( );
}
/**
* Gets a daemon object from its key name
*
* @param strDaemonKey
* The daemon key
* @return The daemon
*/
public static Daemon getDaemon( String strDaemonKey )
{
DaemonEntry entry = _mapDaemonEntries.get( strDaemonKey );
return entry.getDaemon( );
}
/**
* return the OnStartup key link to the daemon
*
* @param strDaemonKey
* The daemon key
* @return The key
*/
private static String getOnStartupKey( String strDaemonKey )
{
return KEY_DAEMON_PREFIX + strDaemonKey + PROPERTY_DAEMON_ON_STARTUP;
}
/**
* return the Interval key link to the daemon
*
* @param strDaemonKey
* The daemon key
* @return The key
*/
private static String getIntervalKey( String strDaemonKey )
{
return KEY_DAEMON_PREFIX + strDaemonKey + PROPERTY_DAEMON_INTERVAL;
}
}