All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.daemon.AbstractDaemon Maven / Gradle / Ivy

Go to download

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:
	// /////////////////////////////////////////////////////////////////////////
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy