org.refcodes.daemon.AbstractDaemon Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of refcodes-daemon Show documentation
Show all versions of refcodes-daemon Show documentation
The refcodes-daemon artifact provides some base functionality for
setting up some tiny services or daemons.
The newest version!
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany and licensed
// under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.daemon;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileAlreadyExistsException;
import java.text.ParseException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.refcodes.data.Delimiter;
import org.refcodes.data.LatencySleepTime;
import org.refcodes.data.Scheme;
import org.refcodes.exception.ExceptionUtility;
import org.refcodes.logger.RuntimeLogger;
import org.refcodes.logger.RuntimeLoggerFactorySingleton;
import org.refcodes.properties.Properties;
import org.refcodes.properties.ResourceProperties;
import org.refcodes.properties.ResourceProperties.ResourcePropertiesBuilder;
import org.refcodes.properties.TomlProperties;
import org.refcodes.properties.TomlPropertiesBuilder;
import org.refcodes.rest.HttpRestClientImpl;
import org.refcodes.rest.HttpRestServer;
import org.refcodes.rest.HttpRestServerImpl;
import org.refcodes.runtime.RuntimeUtility;
import org.refcodes.textual.Case;
import org.refcodes.textual.CaseStyleBuilder;
import org.refcodes.web.BasicAuthCredentials;
import org.refcodes.web.BasicCredentials;
import org.refcodes.web.BasicCredentialsImpl;
import org.refcodes.web.HttpResponseException;
import org.refcodes.web.MediaType;
import org.refcodes.web.PortManagerSingleton;
/**
* The Class AbstractDaemon.
*/
public abstract class AbstractDaemon {
private static RuntimeLogger LOGGER = RuntimeLoggerFactorySingleton.createRuntimeLogger();
// /////////////////////////////////////////////////////////////////////////
// STATICS:
// /////////////////////////////////////////////////////////////////////////
/**
* The Enum ShutDownMode.
*/
private enum ShutDownMode {
STRICT, GRACEFUL
}
// /////////////////////////////////////////////////////////////////////////
// CONSTANTS:
// /////////////////////////////////////////////////////////////////////////
private static final String CONFIG_FILE_SUFFIX = ".ini";
private static final String PROPERTY_PORT = "MANAGEMENT_PORT";
private static final String PROPERTY_SHUTDOWN_PATH = "MANAGEMENT_SHUTDOWN_PATH";
private static final String PROPERTY_USERNAME = "MANAGEMENT_USERNAME";
private static final String PROPERTY_SECRET = "MANAGEMENT_SECRET";
private static final String CONFIG_FILE_NAME = "application.conf";
private static final String DEFAULT_SHUTDOWN_PATH = "/shutdown";
protected static final int EXIT_CODE_OK = 0;
protected static final int EXIT_CODE_ALREADY_STARTED = 1;
protected static final int EXIT_CODE_NOT_RUNNING = 2;
protected static final int EXIT_CANNOT_OPEN_MANAGEMENT_SERVER = 3;
protected static final int EXIT_CANNOT_SAVE_RUNTIME_PROPERTIES = 4;
protected static final int EXIT_CANNOT_LOAD_RUNTIME_PROPERTIES = 5;
protected static final int EXIT_CODE_NOT_STARTED = 6;
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// INJECTION:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
/**
* Do restart.
*/
protected static void doRestart() {
doRestart( CONFIG_FILE_NAME );
}
/**
* Do restart.
*
* @param configFileName the config file name
*/
private static void doRestart( String configFileName ) {
doShutdown( ShutDownMode.GRACEFUL );
doStartup( configFileName );
}
/**
* Do startup.
*/
protected static void doStartup() {
doStartup( CONFIG_FILE_NAME );
}
/**
* Do startup.
*
* @param aConfigFileName the config file name
*/
protected static void doStartup( String aConfigFileName ) {
File theRuntimeFile = toRuntimePropertiesFile();
if ( theRuntimeFile.isFile() ) {
int theMgmPort = -1;
try {
ResourceProperties theRuntimeSettings = new TomlProperties( theRuntimeFile );
theMgmPort = theRuntimeSettings.getInt( PROPERTY_PORT );
if ( theMgmPort != -1 && !PortManagerSingleton.getInstance().isPortAvaialble( theMgmPort ) ) {
LOGGER.error( "A server seems already to be running on port <" + theMgmPort + ">! You cannot start it more than once." );
System.exit( EXIT_CODE_ALREADY_STARTED );
}
LOGGER.error( "A server seems already to be running! You cannot start it more than once." );
System.exit( EXIT_CODE_ALREADY_STARTED );
}
catch ( IOException | ParseException ignore ) {}
}
int theMgmPort = -1;
String theUserName = null;
String theSecret = null;
String theShutdownLocator = null;
try {
Properties theConfiguration = toConfigurationProperties( aConfigFileName );
theUserName = theConfiguration.get( PROPERTY_USERNAME );
theSecret = theConfiguration.get( PROPERTY_SECRET );
theShutdownLocator = theConfiguration.get( PROPERTY_SHUTDOWN_PATH );
try {
theMgmPort = theConfiguration.getInt( PROPERTY_PORT );
}
catch ( Exception ignore ) {}
}
catch ( IOException | ParseException e ) {
LOGGER.info( "No configuration <" + aConfigFileName + "> provided, using default settings: " + ExceptionUtility.toMessage( e ) );
}
theMgmPort = theMgmPort != -1 ? theMgmPort : PortManagerSingleton.getInstance().bindAnyPort();
final BasicCredentials theCredentials = (theUserName != null || theSecret != null) ? new BasicCredentialsImpl( theUserName, theSecret ) : null;
final String theShutdownPath = theShutdownLocator != null ? theShutdownLocator : DEFAULT_SHUTDOWN_PATH;
// ---------------------------------------------------------------------
// Start the shutdown server:
// ---------------------------------------------------------------------
LOGGER.info( "Starting daemon's management server with shutdown path <" + theShutdownPath + "> on port <" + theMgmPort + ">..." );
HttpRestServer theRestServer = new HttpRestServerImpl();
try {
theRestServer.onGet( theShutdownPath, ( aRequest, aResponse ) -> {
if ( theCredentials != null ) {
BasicAuthCredentials theAuthCredentials = aRequest.getHeaderFields().getBasicAuthCredentials();
theAuthCredentials.validate( theCredentials );
}
LOGGER.info( "Shutting down ..." );
aResponse.getHeaderFields().putContentType( MediaType.APPLICATION_JSON );
aResponse.setResponse( "Shutting down ... bye!" );
theRestServer.closeIn( LatencySleepTime.MIN.getTimeInMs() );
Executors.newSingleThreadScheduledExecutor().schedule( () -> {
System.exit( EXIT_CODE_OK );
}, LatencySleepTime.MIN.getTimeInMs(), TimeUnit.MILLISECONDS );
} ).open();
theRestServer.open( theMgmPort );
}
catch ( IOException e ) {
LOGGER.error( "Cannot open the management server on port <" + theMgmPort + ">, aborting: " + ExceptionUtility.toMessage( e ), e );
System.exit( EXIT_CANNOT_OPEN_MANAGEMENT_SERVER );
}
// ---------------------------------------------------------------------
ResourcePropertiesBuilder theProperties = new TomlPropertiesBuilder().withPutInt( PROPERTY_PORT, theMgmPort ).withPut( PROPERTY_SHUTDOWN_PATH, theShutdownPath );
theRuntimeFile.deleteOnExit();
try {
theProperties.saveTo( theRuntimeFile );
}
catch ( IOException e ) {
LOGGER.error( "Cannot save runtime properties to file <" + theRuntimeFile + ">, aborting: " + ExceptionUtility.toMessage( e ), e );
System.exit( EXIT_CANNOT_SAVE_RUNTIME_PROPERTIES );
}
}
/**
* Do shutdown.
*/
protected static void doShutdown() {
doShutdown( ShutDownMode.STRICT );
}
/**
* Do shutdown.
*
* @param aShutDowenMode the shut dowen mode
*/
protected static void doShutdown( ShutDownMode aShutDowenMode ) {
File theRuntimeFile = toRuntimePropertiesFile();
if ( !theRuntimeFile.isFile() ) {
if ( aShutDowenMode == ShutDownMode.STRICT ) {
LOGGER.info( "The server seems not to be running! You must start the server before shutting down." );
System.exit( EXIT_CODE_NOT_STARTED );
}
}
Properties theRuntimeSettings;
try {
theRuntimeSettings = new TomlProperties( theRuntimeFile );
theRuntimeFile.deleteOnExit();
String theShutdownPath = theRuntimeSettings.get( PROPERTY_SHUTDOWN_PATH );
theShutdownPath = theShutdownPath != null ? theShutdownPath : DEFAULT_SHUTDOWN_PATH;
LOGGER.info( "Shutting down daemon with management server's shutdown path <" + theShutdownPath + "> on port <" + theRuntimeSettings.getInt( PROPERTY_PORT ) + "> ..." );
try {
new HttpRestClientImpl().doGet( Scheme.HTTP, "localhost", theRuntimeSettings.getInt( PROPERTY_PORT ), theShutdownPath );
}
catch ( HttpResponseException e ) {
if ( aShutDowenMode == ShutDownMode.STRICT ) {
LOGGER.info( "No daemon running with shutdown path <" + theShutdownPath + "> on port <" + theRuntimeSettings.get( PROPERTY_PORT ) + ">, nothing to stop." );
System.exit( EXIT_CODE_NOT_RUNNING );
}
}
catch ( NumberFormatException e ) {
LOGGER.error( "Cannot shut down via shutdown path <" + theShutdownPath + "> on port <" + theRuntimeSettings.get( PROPERTY_PORT ) + ">:" + ExceptionUtility.toMessage( e ), e );
System.exit( EXIT_CANNOT_LOAD_RUNTIME_PROPERTIES );
}
try {
Thread.sleep( LatencySleepTime.MIN.getTimeInMs() );
}
catch ( InterruptedException ignore ) {}
if ( aShutDowenMode == ShutDownMode.STRICT ) {
System.exit( EXIT_CODE_OK );
}
}
catch ( IOException | ParseException e ) {
if ( aShutDowenMode == ShutDownMode.STRICT ) {
LOGGER.error( "Cannot load runtime properties from file <" + theRuntimeFile + ">, aborting: " + ExceptionUtility.toMessage( e ), e );
System.exit( EXIT_CANNOT_LOAD_RUNTIME_PROPERTIES );
}
}
}
/**
* To runtime properties file.
*
* @return the file
*/
protected static File toRuntimePropertiesFile() {
String theBaseConfigFileName = getBaseConfigFileName();
return new File( RuntimeUtility.toLauncherDir(), theBaseConfigFileName );
}
/**
* Determines the best fit for a base configuration file name.
*
* @return The fest fit file name.
*/
protected static String getBaseConfigFileName() {
Class> theMainClass = RuntimeUtility.getMainClass();
String theBaseConfigFileName = theMainClass.getPackage().getName();
if ( theBaseConfigFileName != null && theBaseConfigFileName.length() != 0 ) {
int index = theBaseConfigFileName.lastIndexOf( Delimiter.PACKAGE_HIERARCHY.getChar() );
if ( index != -1 ) {
theBaseConfigFileName = theBaseConfigFileName.substring( index );
}
}
if ( theBaseConfigFileName == null || theBaseConfigFileName.length() == 0 ) {
theBaseConfigFileName = theMainClass.getSimpleName();
}
theBaseConfigFileName = new CaseStyleBuilder().withCase( Case.LOWER ).toKebabCase( theBaseConfigFileName ).toLowerCase() + CONFIG_FILE_SUFFIX;
return theBaseConfigFileName;
}
/**
* To configuration properties.
*
* @param aConfigFileName the config file name
*
* @return the properties
*
* @throws IOException thrown in case accessing or processing the properties
* file failed.
* @throws ParseException Signals that an error has been reached
* unexpectedly while parsing the data to be loaded.
*/
protected static Properties toConfigurationProperties( String aConfigFileName ) throws IOException, ParseException {
Properties theConfiguration = new TomlProperties( aConfigFileName );
return theConfiguration;
}
/**
* Writes out a sample configuration file located in the root of your
* resources folder.
*
* @param aConfigFileName The file name of the configuration file found in
* your resources folder.
*
* @return The {@link File} with the sample configuration.
*
* @throws FileAlreadyExistsException Thrown in case an according
* configuration file already exists.
* @throws IOException Thrown in case there were problems writing out the
* file.
*/
protected static File writeConfigFile( String aConfigFileName ) throws FileAlreadyExistsException, IOException {
File theConfigFile = new File( RuntimeUtility.toLauncherDir(), aConfigFileName );
if ( theConfigFile.exists() ) {
throw new FileAlreadyExistsException( theConfigFile.getAbsolutePath(), theConfigFile.getAbsolutePath(), "A file with the same name already exists in the targeted path." );
}
InputStream theInStream = AbstractDaemon.class.getResourceAsStream( "/" + aConfigFileName );
try (OutputStream theOutStream = new FileOutputStream( theConfigFile )) {
byte[] theBuffer = new byte[theInStream.available()];
theInStream.read( theBuffer );
theOutStream.write( theBuffer );
theOutStream.flush();
}
return theConfigFile;
}
// /////////////////////////////////////////////////////////////////////////
// INNER CLASSES:
// /////////////////////////////////////////////////////////////////////////
}