472 lines
16 KiB
Java
472 lines
16 KiB
Java
package li.strolch.agent.api;
|
|
|
|
import static java.util.Objects.requireNonNull;
|
|
|
|
import java.io.File;
|
|
import java.io.InputStream;
|
|
import java.text.MessageFormat;
|
|
import java.util.Arrays;
|
|
import java.util.Properties;
|
|
|
|
import li.strolch.runtime.configuration.ConfigurationParser;
|
|
import li.strolch.runtime.configuration.StrolchConfigurationException;
|
|
import li.strolch.runtime.configuration.StrolchEnvironment;
|
|
import li.strolch.utils.dbc.DBC;
|
|
import li.strolch.utils.helper.FileHelper;
|
|
import li.strolch.utils.helper.StringHelper;
|
|
import li.strolch.utils.helper.XmlHelper;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.xml.sax.Attributes;
|
|
import org.xml.sax.SAXException;
|
|
import org.xml.sax.helpers.DefaultHandler;
|
|
|
|
public class StrolchBootstrapper extends DefaultHandler {
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(StrolchBootstrapper.class);
|
|
|
|
public static final String APP_VERSION_PROPERTIES = "/appVersion.properties"; //$NON-NLS-1$
|
|
|
|
private static final String SYS_PROP_USER_DIR = "user.dir";
|
|
private static final String STROLCH_BOOTSTRAP = "StrolchBootstrap";
|
|
private static final String ENV = "env";
|
|
|
|
private static final String ID = "id";
|
|
private static final String DEFAULT = "default";
|
|
|
|
private static final String ENVIRONMENT = "environment";
|
|
private static final String ROOT = "root";
|
|
private static final String CONFIG = "config";
|
|
private static final String DATA = "data";
|
|
private static final String TEMP = "temp";
|
|
|
|
public static final String FILE_BOOTSTRAP = "StrolchBootstrap.xml";
|
|
|
|
public static final String PATH_CONFIG = "config"; //$NON-NLS-1$
|
|
public static final String PATH_DATA = "data"; //$NON-NLS-1$
|
|
public static final String PATH_TEMP = "temp"; //$NON-NLS-1$
|
|
|
|
// input
|
|
private String environment;
|
|
|
|
// intermediary
|
|
private boolean defaultAllowed;
|
|
private String environmentOverride;
|
|
private boolean envFound;
|
|
private boolean insideEnv;
|
|
private StringBuilder textB;
|
|
|
|
private String rootS;
|
|
private String configS;
|
|
private String dataS;
|
|
private String tempS;
|
|
|
|
// result
|
|
private File configPathF;
|
|
private File dataPathF;
|
|
private File tempPathF;
|
|
|
|
private StrolchVersion appVersion;
|
|
|
|
/**
|
|
* <p>
|
|
* Bootstrap Strolch using the given app {@link StrolchVersion}. This version is used for information on the code
|
|
* base from which the agent is instantiated.
|
|
* </p>
|
|
*
|
|
* @param appVersion
|
|
* the app's version
|
|
*/
|
|
public StrolchBootstrapper(StrolchVersion appVersion) {
|
|
DBC.PRE.assertNotNull("appVersion must be set!", appVersion);
|
|
this.appVersion = appVersion;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Bootstrap Strolch using the given {@link Class} from which to get the {@link #APP_VERSION_PROPERTIES} resource
|
|
* stream. The version is used for information on the code base from which the agent is instantiated.
|
|
* </p>
|
|
*
|
|
* @param appClass
|
|
* the class where the {@link #APP_VERSION_PROPERTIES} resource resides
|
|
*/
|
|
public StrolchBootstrapper(Class<?> appClass) {
|
|
DBC.PRE.assertNotNull("appClass must be set!", appClass);
|
|
|
|
Properties env = new Properties();
|
|
try (InputStream in = appClass.getResourceAsStream(APP_VERSION_PROPERTIES)) {
|
|
env.load(in);
|
|
} catch (Exception e) {
|
|
throw new IllegalArgumentException(
|
|
"Could not find resource " + APP_VERSION_PROPERTIES + " on ClassLoader of class " + appClass);
|
|
}
|
|
|
|
this.appVersion = new StrolchVersion(env);
|
|
}
|
|
|
|
public void setEnvironmentOverride(String environmentOverride) {
|
|
this.environmentOverride = environmentOverride;
|
|
}
|
|
|
|
public StrolchAgent setupByUserDir(String environment, String subPath) {
|
|
DBC.PRE.assertNotEmpty("Environment must be set!", environment);
|
|
DBC.PRE.assertNotEmpty("Sub Path must be set!", subPath);
|
|
this.environment = environment;
|
|
|
|
File rootPathF = new File(System.getProperty(SYS_PROP_USER_DIR), subPath);
|
|
this.configPathF = new File(rootPathF, PATH_CONFIG);
|
|
this.dataPathF = new File(rootPathF, PATH_DATA);
|
|
this.tempPathF = new File(rootPathF, PATH_TEMP);
|
|
|
|
return setup();
|
|
}
|
|
|
|
public StrolchAgent setupByRoot(String environment, File rootPath) {
|
|
DBC.PRE.assertNotEmpty("Environment must be set!", environment);
|
|
DBC.PRE.assertNotNull("rootPath must be set!", rootPath);
|
|
this.environment = environment;
|
|
|
|
this.configPathF = new File(rootPath, PATH_CONFIG);
|
|
this.dataPathF = new File(rootPath, PATH_DATA);
|
|
this.tempPathF = new File(rootPath, PATH_TEMP);
|
|
|
|
return setup();
|
|
}
|
|
|
|
public StrolchAgent setupByCopyingRoot(String environment, File rootSrcPath, File rootDstPath) {
|
|
DBC.PRE.assertNotEmpty("Environment must be set!", environment);
|
|
DBC.PRE.assertNotNull("rootPath must be set!", rootSrcPath);
|
|
DBC.PRE.assertNotNull("rootPath must be set!", rootDstPath);
|
|
|
|
this.environment = environment;
|
|
|
|
// root path: readable directory
|
|
if (!rootSrcPath.isDirectory() || !rootSrcPath.canRead()) {
|
|
String msg = "[{0}] Root src path is not readable at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, rootSrcPath);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
|
|
// Make sure config exists in this root src
|
|
File configPathF = new File(rootSrcPath, PATH_CONFIG);
|
|
File configurationFile = new File(configPathF, ConfigurationParser.STROLCH_CONFIGURATION_XML);
|
|
if (!configurationFile.isFile() || !configurationFile.canRead()) {
|
|
String msg = "[{0}] Source Configuration file is not readable at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, configurationFile);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
|
|
// if destination exists, make sure it is a directory and empty
|
|
if (rootDstPath.exists()) {
|
|
if (!rootDstPath.isDirectory()) {
|
|
String msg = "[{0}] Destination root exists and is not a directory at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, rootDstPath.getAbsolutePath());
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
if (requireNonNull(rootDstPath.list()).length != 0) {
|
|
String msg = "[{0}] Destination root exists and is not empty at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, rootDstPath.getAbsolutePath());
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
} else if (!rootDstPath.mkdir()) {
|
|
String msg = "[{0}] Destination root does not exist and could not be created. Either parent does not exist, or permission is denied at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, rootDstPath.getAbsolutePath());
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
|
|
String msg = "[{0}] Copying source {1} to {2}"; //$NON-NLS-1$
|
|
logger.info(
|
|
MessageFormat.format(msg, environment, rootSrcPath.getAbsolutePath(), rootDstPath.getAbsolutePath()));
|
|
|
|
if (!FileHelper.copy(rootSrcPath.listFiles(), rootDstPath, true)) {
|
|
msg = "[{0}] Failed to copy source files from {1} to {2}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, rootSrcPath.getAbsolutePath(), rootDstPath.getAbsolutePath());
|
|
throw new RuntimeException(msg);
|
|
}
|
|
|
|
this.configPathF = new File(rootDstPath, PATH_CONFIG);
|
|
this.dataPathF = new File(rootDstPath, PATH_DATA);
|
|
this.tempPathF = new File(rootDstPath, PATH_TEMP);
|
|
|
|
return setup();
|
|
}
|
|
|
|
/**
|
|
* Set up Strolch by evaluating the environment from {@link StrolchEnvironment#getEnvironmentFromResourceEnv(Class)}
|
|
* and then delegating to {@link #setupByBootstrapFile(String, File)}
|
|
*
|
|
* @param clazz
|
|
* the class from which to load the resource as stream
|
|
* @param bootstrapFile
|
|
* the bootstrap file to load
|
|
*
|
|
* @return the Agent which is setup
|
|
*/
|
|
public StrolchAgent setupByBootstrapFile(Class<?> clazz, File bootstrapFile) {
|
|
DBC.PRE.assertNotNull("clazz must be set!", clazz);
|
|
DBC.PRE.assertNotNull("bootstrapFile must be set!", bootstrapFile);
|
|
this.environment = StrolchEnvironment.getEnvironmentFromResourceEnv(clazz);
|
|
parseBoostrapFile(bootstrapFile);
|
|
return setup();
|
|
}
|
|
|
|
/**
|
|
* Set up Strolch by evaluating the environment from {@link StrolchEnvironment#getEnvironmentFromResourceEnv(Class)}
|
|
* and then delegating to {@link #setupByBootstrapFile(String, File)}
|
|
*
|
|
* @param clazz
|
|
* the class from which to load the resource as stream
|
|
* @param bootstrapFile
|
|
* the input stream to the bootstrap file to load
|
|
*
|
|
* @return the Agent which is setup
|
|
*/
|
|
public StrolchAgent setupByBootstrapFile(Class<?> clazz, InputStream bootstrapFile) {
|
|
DBC.PRE.assertNotNull("clazz must be set!", clazz);
|
|
DBC.PRE.assertNotNull("bootstrapFile must be set!", bootstrapFile);
|
|
this.environment = StrolchEnvironment.getEnvironmentFromResourceEnv(clazz);
|
|
parseBoostrapFile(bootstrapFile);
|
|
return setup();
|
|
}
|
|
|
|
/**
|
|
* Set up Strolch by loading the given bootstrap file for configuration
|
|
*
|
|
* @param environment
|
|
* the environment to load from the boostrap file
|
|
* @param bootstrapFile
|
|
* the bootstrap file to load
|
|
*
|
|
* @return the Agent which is setup
|
|
*/
|
|
public StrolchAgent setupByBootstrapFile(String environment, File bootstrapFile) {
|
|
DBC.PRE.assertNotEmpty("Environment must be set!", environment);
|
|
DBC.PRE.assertNotNull("bootstrapFile must be set!", bootstrapFile);
|
|
this.environment = environment;
|
|
parseBoostrapFile(bootstrapFile);
|
|
return setup();
|
|
}
|
|
|
|
/**
|
|
* Set up Strolch by loading the given bootstrap file for configuration
|
|
*
|
|
* @param environment
|
|
* the environment to load from the boostrap file
|
|
* @param bootstrapFile
|
|
* the input stream to the bootstrap file to load
|
|
*
|
|
* @return the Agent which is setup
|
|
*/
|
|
public StrolchAgent setupByBootstrapFile(String environment, InputStream bootstrapFile) {
|
|
DBC.PRE.assertNotEmpty("Environment must be set!", environment);
|
|
DBC.PRE.assertNotNull("bootstrapFile must be set!", bootstrapFile);
|
|
this.environment = environment;
|
|
parseBoostrapFile(bootstrapFile);
|
|
return setup();
|
|
}
|
|
|
|
private StrolchAgent setup() {
|
|
|
|
DBC.PRE.assertNotEmpty("Environment must be set!", this.environment);
|
|
DBC.PRE.assertNotNull("configPathF must be set!", this.configPathF); //$NON-NLS-1$
|
|
DBC.PRE.assertNotNull("dataPathF must be set!", this.dataPathF); //$NON-NLS-1$
|
|
DBC.PRE.assertNotNull("tempPathF must be set!", this.tempPathF); //$NON-NLS-1$
|
|
|
|
// config path: readable directory
|
|
if (!this.configPathF.isDirectory() || !this.configPathF.canRead()) {
|
|
String msg = "[{0}] Config path is not readable at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, this.configPathF);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
// configuration file must exist
|
|
// get path to configuration file
|
|
File configurationFile = new File(this.configPathF, ConfigurationParser.STROLCH_CONFIGURATION_XML);
|
|
if (!configurationFile.isFile() || !configurationFile.canRead()) {
|
|
String msg = "[{0}] Configuration file is not readable at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, configurationFile);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
|
|
// data path: writable directory
|
|
if (!this.dataPathF.exists() && !this.dataPathF.mkdir()) {
|
|
String msg = "[{0}] Could not create missing data path at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, this.dataPathF);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
if (!this.dataPathF.isDirectory() || !this.dataPathF.canRead() || !this.dataPathF.canWrite()) {
|
|
String msg = "[{0}] Data path is not a directory or readable or writeable at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, this.dataPathF);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
|
|
// tmp path: writable directory
|
|
if (!this.tempPathF.exists() && !this.tempPathF.mkdir()) {
|
|
String msg = "[{0}] Could not create missing temp path at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, this.tempPathF);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
if (!this.tempPathF.isDirectory() || !this.tempPathF.canRead() || !this.tempPathF.canWrite()) {
|
|
String msg = "[{0}] Temp path is not a directory or readable or writeable at {1}"; //$NON-NLS-1$
|
|
msg = MessageFormat.format(msg, environment, this.tempPathF);
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
|
|
LoggingLoader.reloadLogging(this.configPathF);
|
|
|
|
String env;
|
|
if (StringHelper.isEmpty(this.environmentOverride)) {
|
|
env = this.environment;
|
|
} else {
|
|
String msg = "[{0}] Environment override to ''{1}''";
|
|
logger.info(MessageFormat.format(msg, this.environment, this.environmentOverride));
|
|
env = this.environmentOverride;
|
|
}
|
|
|
|
StrolchAgent agent = new StrolchAgent(this.appVersion);
|
|
agent.setup(env, this.configPathF, this.dataPathF, this.tempPathF);
|
|
return agent;
|
|
}
|
|
|
|
private void parseBoostrapFile(File bootstrapFile) {
|
|
// parse the document using ourselves as the DefaultHandler
|
|
XmlHelper.parseDocument(bootstrapFile, this);
|
|
|
|
if (!this.envFound) {
|
|
throw new StrolchConfigurationException(
|
|
"Environment " + this.environment + " not configured in bootstrap configuration " + bootstrapFile
|
|
.getAbsolutePath());
|
|
}
|
|
|
|
evaluatePaths();
|
|
}
|
|
|
|
private void parseBoostrapFile(InputStream bootstrapStream) {
|
|
|
|
// parse the document using ourselves as the DefaultHandler
|
|
XmlHelper.parseDocument(bootstrapStream, this);
|
|
|
|
if (!this.envFound) {
|
|
throw new StrolchConfigurationException("Environment " + this.environment
|
|
+ " not configured in bootstrap configuration from given stream!");
|
|
}
|
|
|
|
evaluatePaths();
|
|
}
|
|
|
|
private void evaluatePaths() {
|
|
|
|
// validate the parsed data
|
|
if (!this.defaultAllowed) {
|
|
if (StringHelper.isEmpty(this.configS) || StringHelper.isEmpty(this.dataS) || StringHelper
|
|
.isEmpty(this.tempS)) {
|
|
String msg = "One element of " + Arrays.toString(new String[] { CONFIG, DATA, TEMP })
|
|
+ " is not set and environment " + this.environment + " does not have attribute " + DEFAULT
|
|
+ "=\"true\". Either set the value or allow using default values!";
|
|
throw new StrolchConfigurationException(msg);
|
|
}
|
|
}
|
|
|
|
String root = StringHelper.isEmpty(this.rootS) ?
|
|
new File(System.getProperty(SYS_PROP_USER_DIR)).getAbsolutePath() :
|
|
this.rootS;
|
|
String config = StringHelper.isEmpty(this.configS) ? PATH_CONFIG : this.configS;
|
|
String data = StringHelper.isEmpty(this.dataS) ? PATH_DATA : this.dataS;
|
|
String temp = StringHelper.isEmpty(this.tempS) ? PATH_TEMP : this.tempS;
|
|
|
|
File rootPathF = new File(root);
|
|
|
|
File tmp;
|
|
|
|
tmp = new File(config);
|
|
this.configPathF = tmp.isAbsolute() ? tmp : new File(rootPathF, config);
|
|
|
|
tmp = new File(data);
|
|
this.dataPathF = tmp.isAbsolute() ? tmp : new File(rootPathF, data);
|
|
|
|
tmp = new File(temp);
|
|
this.tempPathF = tmp.isAbsolute() ? tmp : new File(rootPathF, temp);
|
|
}
|
|
|
|
@Override
|
|
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
|
|
|
switch (qName) {
|
|
case STROLCH_BOOTSTRAP:
|
|
break;
|
|
|
|
case ENV:
|
|
if (attributes.getValue(ID).equals(this.environment)) {
|
|
this.insideEnv = true;
|
|
this.envFound = true;
|
|
} else {
|
|
this.insideEnv = false;
|
|
}
|
|
|
|
String defaultS = attributes.getValue(DEFAULT);
|
|
this.defaultAllowed = defaultS == null ? false : StringHelper.parseBoolean(defaultS);
|
|
|
|
break;
|
|
|
|
case ENVIRONMENT:
|
|
case ROOT:
|
|
case CONFIG:
|
|
case DATA:
|
|
case TEMP:
|
|
|
|
if (this.insideEnv)
|
|
this.textB = new StringBuilder();
|
|
|
|
break;
|
|
|
|
default:
|
|
throw new StrolchConfigurationException("Unhandled element " + qName);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void endElement(String uri, String localName, String qName) throws SAXException {
|
|
|
|
switch (qName) {
|
|
case STROLCH_BOOTSTRAP:
|
|
break;
|
|
|
|
case ENV:
|
|
this.insideEnv = false;
|
|
break;
|
|
|
|
case ENVIRONMENT:
|
|
if (this.insideEnv)
|
|
this.environmentOverride = this.textB.toString();
|
|
break;
|
|
case ROOT:
|
|
if (this.insideEnv)
|
|
this.rootS = this.textB.toString();
|
|
break;
|
|
case CONFIG:
|
|
if (this.insideEnv)
|
|
this.configS = this.textB.toString();
|
|
break;
|
|
case DATA:
|
|
if (this.insideEnv)
|
|
this.dataS = this.textB.toString();
|
|
break;
|
|
case TEMP:
|
|
if (this.insideEnv)
|
|
this.tempS = this.textB.toString();
|
|
break;
|
|
|
|
default:
|
|
throw new StrolchConfigurationException("Unhandled element " + qName);
|
|
}
|
|
|
|
this.textB = null;
|
|
}
|
|
|
|
@Override
|
|
public void characters(char[] ch, int start, int length) throws SAXException {
|
|
if (this.textB != null)
|
|
this.textB.append(ch, start, length);
|
|
}
|
|
}
|