/*
* Copyright 2008-2014 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaleidofoundry.core.env;
import static org.kaleidofoundry.core.env.model.EnvironmentConstants.CACHE_PROVIDER_PROPERTY;
import static org.kaleidofoundry.core.env.model.EnvironmentConstants.CONFIGURATIONS_PROPERTY;
import static org.kaleidofoundry.core.env.model.EnvironmentConstants.CONFIGURATIONS_PROPERTY_SEPARATOR;
import static org.kaleidofoundry.core.env.model.EnvironmentConstants.DEFAULT_BASE_DIR_PROPERTY;
import static org.kaleidofoundry.core.env.model.EnvironmentConstants.KALEIDO_PERSISTENT_UNIT_NAME;
import static org.kaleidofoundry.core.env.model.EnvironmentConstants.LOCAL_PROPERTY;
import static org.kaleidofoundry.core.env.model.EnvironmentConstants.STATIC_ENV_PARAMETERS;
import static org.kaleidofoundry.core.i18n.InternalBundleHelper.CoreMessageBundle;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.annotation.PostConstruct;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import org.kaleidofoundry.core.cache.CacheManager;
import org.kaleidofoundry.core.cache.CacheManagerFactory;
import org.kaleidofoundry.core.cache.CacheManagerProvider;
import org.kaleidofoundry.core.config.Configuration;
import org.kaleidofoundry.core.config.ConfigurationFactory;
import org.kaleidofoundry.core.config.NamedConfigurationInitializer;
import org.kaleidofoundry.core.env.model.EnvironmentConstants;
import org.kaleidofoundry.core.env.model.EnvironmentEntry;
import org.kaleidofoundry.core.env.model.EnvironmentInfo;
import org.kaleidofoundry.core.env.model.EnvironmentStatus;
import org.kaleidofoundry.core.env.model.EnvironmentStatus.Status;
import org.kaleidofoundry.core.env.model.EnvironmentVersions;
import org.kaleidofoundry.core.persistence.UnmanagedEntityManagerFactory;
import org.kaleidofoundry.core.plugin.PluginFactory;
import org.kaleidofoundry.core.plugin.model.Plugin;
import org.kaleidofoundry.core.store.FileStore;
import org.kaleidofoundry.core.store.FileStoreProvider;
import org.kaleidofoundry.core.store.ResourceException;
import org.kaleidofoundry.core.system.OsEnvironment;
import org.kaleidofoundry.core.util.ReflectionHelper;
import org.kaleidofoundry.core.util.StringHelper;
import org.kaleidofoundry.core.util.ThrowableHelper;
import org.kaleidofoundry.core.util.locale.LocaleFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Environment Initializer :<br/>
* The sequence to call is :
* <ul>
* <li>{@link #init()} : load environments properties (Operating system variables / java system variables ...)</li>
* <li>{@link #start()} : start the init of some components like : {@link FileStore} / {@link Configuration} / {@link CacheManager}</li>
* <li>{@link #stop()} : stop and free the components started</li>
* </ul>
*
* @see EnvironmentStatus
* @see EnvironmentInfo
* @see EnvironmentConstants
*
* @author jraduget
*/
@Stateless
// @Singleton
public class EnvironmentInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(EnvironmentInitializer.class);
static EnvironmentInitializer instance;
private final Class<?> applicationClass;
private final Logger logger;
// static shared variables, because of the javax rest controller create multiple instances at each request
private static Status status;
private static Throwable error;
// configuration load by annotation
private NamedConfigurationInitializer configurationInitializer;
/** injected entity manager */
@PersistenceContext(unitName = KALEIDO_PERSISTENT_UNIT_NAME)
EntityManager em;
public EnvironmentInitializer() {
this(EnvironmentInitializer.class);
}
public EnvironmentInitializer(final Class<?> applicationClass) {
if (instance != null) { throw new IllegalStateException("environment have already been initialized"); }
this.applicationClass = applicationClass;
this.logger = applicationClass == null ? LOGGER : LoggerFactory.getLogger(applicationClass);
status = Status.STOPPED;
}
/**
* @return application status
*/
public EnvironmentInfo getInfo() {
String applicationVersion = applicationClass != null ? applicationClass.getPackage().getImplementationVersion() : null;
String kaleidoVersion = this.getClass().getPackage().getImplementationVersion();
String runnerVersion = Thread.currentThread().getClass().getPackage().getImplementationVersion();
if (runnerVersion == null) {
runnerVersion = Thread.currentThread().getClass().getPackage().getSpecificationVersion();
}
EnvironmentVersions versions = new EnvironmentVersions(applicationVersion, runnerVersion, kaleidoVersion);
List<EnvironmentEntry> manifestInfos = new ArrayList<EnvironmentEntry>();
List<EnvironmentEntry> runnerManifestInfos = new ArrayList<EnvironmentEntry>();
List<EnvironmentEntry> osInfos = new ArrayList<EnvironmentEntry>();
List<EnvironmentEntry> systemInfos = new ArrayList<EnvironmentEntry>();
List<Plugin<?>> plugins = new ArrayList<Plugin<?>>();
List<Plugin<?>> pluginImpls = new ArrayList<Plugin<?>>();
plugins.addAll(PluginFactory.getInterfaceRegistry().values());
pluginImpls.addAll(PluginFactory.getImplementationRegistry().values());
try {
final OsEnvironment environment = new OsEnvironment();
for (final String key : environment.stringPropertyNames()) {
osInfos.add(new EnvironmentEntry(key, environment.getProperty(key)));
}
} catch (Throwable th) {
LOGGER.error("Error retrieving the OS environment informations", th);
}
try {
for (String propName : System.getProperties().stringPropertyNames()) {
systemInfos.add(new EnvironmentEntry(propName, System.getProperty(propName)));
}
} catch (Throwable th) {
LOGGER.error("Error retrieving the java system properties informations", th);
}
try {
InputStream inputStream = applicationClass.getResourceAsStream("classpath:/META-INF/MANIFEST.MF");
if (inputStream != null) {
Manifest manifest = new Manifest(inputStream);
for (Entry<Object, Object> entry : manifest.getMainAttributes().entrySet()) {
manifestInfos.add(new EnvironmentEntry(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())));
}
for (Entry<String, Attributes> entry : manifest.getEntries().entrySet()) {
manifestInfos.add(new EnvironmentEntry(entry.getKey(), entry.getValue().toString()));
}
}
} catch (Throwable th) {
LOGGER.error("Error retrieving the manifest informations", th);
}
try {
InputStream inputStream = Thread.currentThread().getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
if (inputStream != null) {
Manifest manifest = new Manifest(inputStream);
for (Entry<Object, Object> entry : manifest.getMainAttributes().entrySet()) {
runnerManifestInfos.add(new EnvironmentEntry(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())));
}
for (Entry<String, Attributes> entry : manifest.getEntries().entrySet()) {
runnerManifestInfos.add(new EnvironmentEntry(entry.getKey(), entry.getValue().toString()));
}
}
} catch (Throwable th) {
LOGGER.error("Error retrieving the manifest informations of the runner", th);
}
return new EnvironmentInfo(versions, manifestInfos, runnerManifestInfos, systemInfos, osInfos, plugins, pluginImpls);
}
/**
* @return application environment
*/
public EnvironmentStatus getStatus() {
return new EnvironmentStatus(status, ThrowableHelper.getStackTrace(error));
}
/**
* Load environment settings before {@link #start()} <br/>
* It can be overloaded
*/
@PostConstruct
public synchronized void init() {
// em will be injected by by java ee container or if not by aspectj
try {
if (em == null) {
em = UnmanagedEntityManagerFactory.currentEntityManager(KALEIDO_PERSISTENT_UNIT_NAME);
}
} catch (PersistenceException pe) {
em = null;
}
// Plugin registry loading
PluginFactory.getInterfaceRegistry();
logger.info(StringHelper.replicate("*", 120));
// First load system env variables
try {
final OsEnvironment environment = new OsEnvironment();
for (final String key : environment.stringPropertyNames()) {
final String value = environment.getProperty(key);
STATIC_ENV_PARAMETERS.put(key, value != null ? value : "");
}
} catch (IOException ioe) {
logger.error("Unable to load operating system environment", ioe);
}
// Then, load java system variables
final Properties javaEnvVariables = System.getProperties();
for (final String key : javaEnvVariables.stringPropertyNames()) {
STATIC_ENV_PARAMETERS.put(key, javaEnvVariables.getProperty(key));
}
status = Status.INIT;
// memorize current instance
instance = this;
}
/**
* @return map of parameters by name
*/
public Map<String, String> getEnvironments() {
return STATIC_ENV_PARAMETERS;
}
/**
* start and load I18n / FileStore / CacheManager / Configuration <br/>
*
* @throws IllegalStateException if you try to start it from an invalid state (started, error)
*/
public synchronized EnvironmentInitializer start() throws IllegalStateException {
if (status == Status.STOPPED) {
init();
}
if (status == Status.INIT) {
try {
// Merge static environment variable values, to have the final value
for (Entry<String, String> entry : STATIC_ENV_PARAMETERS.entrySet()) {
LOGGER.debug("Define environment variable {}={}", entry.getKey(), entry.getValue());
STATIC_ENV_PARAMETERS.put(entry.getKey(), StringHelper.resolveExpression(entry.getValue(), STATIC_ENV_PARAMETERS));
}
// I18n JPA enable or no
if (org.kaleidofoundry.core.i18n.I18nMessagesProvider.isJpaEnabledForI18n()) {
LOGGER.info(CoreMessageBundle.getMessage("loader.define.i18n.jpa.enabled"));
} else {
LOGGER.info(CoreMessageBundle.getMessage("loader.define.i18n.jpa.disabled"));
}
// Parse and set default locale if needed
final String defaultLocale = STATIC_ENV_PARAMETERS.get(LOCAL_PROPERTY);
if (!StringHelper.isEmpty(defaultLocale)) {
LOGGER.info(CoreMessageBundle.getMessage("loader.define.locale", defaultLocale));
final Locale setDefaultLocale = LocaleFactory.parseLocale(defaultLocale);
Locale.setDefault(setDefaultLocale);
LOGGER.info(StringHelper.replicate("*", 120));
}
// FileStore init (basedir ...)
FileStoreProvider.init(STATIC_ENV_PARAMETERS.get(DEFAULT_BASE_DIR_PROPERTY));
// Cache provider init (default cache provider ...)
CacheManagerProvider.init(STATIC_ENV_PARAMETERS.get(CACHE_PROVIDER_PROPERTY));
// Configurations to load
final String kaleidoConfigurations = STATIC_ENV_PARAMETERS.get(CONFIGURATIONS_PROPERTY);
if (!StringHelper.isEmpty(kaleidoConfigurations)) {
LOGGER.info(CoreMessageBundle.getMessage("loader.define.configurations",
StringHelper.replaceAll(kaleidoConfigurations, "\n", ",").replaceAll("\\s+", "")));
// load and register given configurations ids / url
try {
ConfigurationFactory.init(StringHelper.replaceAll(kaleidoConfigurations, "\n", CONFIGURATIONS_PROPERTY_SEPARATOR));
} catch (final ResourceException rse) {
throw new IllegalStateException(CoreMessageBundle.getMessage("loader.define.configurations.error", kaleidoConfigurations), rse);
}
LOGGER.info(StringHelper.replicate("*", 120));
}
if (applicationClass != null) {
// load the configuration annotations present in the main class
configurationInitializer = new NamedConfigurationInitializer(applicationClass);
configurationInitializer.init();
}
status = Status.STARTED;
return this;
} catch (RuntimeException re) {
status = Status.ERROR;
error = re;
throw re;
}
} else {
throw new IllegalStateException("Environment could not be started. The current state is " + status.name());
}
}
/**
* stop and free resources
*
* @throws IllegalStateException if you try to start it from an invalid state (started, error)
*/
public synchronized EnvironmentInitializer stop() {
if (status == Status.STARTED || status == Status.ERROR) {
try {
// unload messaging resources if it is in the classpath
try {
Class<?> transportClass = Class.forName("org.kaleidofoundry.messaging.TransportFactory");
Method closeAllMethod = ReflectionHelper.getMethodWithNoArgs(transportClass, "closeAll");
ReflectionHelper.invokeStaticMethodSilently(closeAllMethod);
} catch (ClassNotFoundException cnfe) {
}
// unload and unregister given configurations ids / url
ConfigurationFactory.unregisterAll();
// unload all cache manager instances
CacheManagerFactory.destroyAll();
// if unmanaged entity manager used, clean all
UnmanagedEntityManagerFactory.closeAll();
status = Status.STOPPED;
instance = null;
return this;
} catch (final ResourceException rse) {
error = rse;
status = Status.ERROR;
throw new IllegalStateException(rse);
} catch (RuntimeException re) {
error = re;
status = Status.ERROR;
throw re;
}
} else {
throw new IllegalStateException("Environment could not be stop. The current state is " + status.name());
}
}
Logger getLogger() {
return logger;
}
}