/* * Copyright 2013 NGDATA nv * Copyright 2007 Outerthought bvba and Schaubroeck nv * * 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.lilyproject.runtime; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.jci.monitor.FilesystemAlterationMonitor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.lilyproject.runtime.classloading.ClassLoaderBuilder; import org.lilyproject.runtime.classloading.ClasspathEntry; import org.lilyproject.runtime.conf.Conf; import org.lilyproject.runtime.configuration.ConfManager; import org.lilyproject.runtime.model.ConfigError; import org.lilyproject.runtime.model.LilyRuntimeModel; import org.lilyproject.runtime.model.LilyRuntimeModelBuilder; import org.lilyproject.runtime.model.ModuleDefinition; import org.lilyproject.runtime.model.SourceLocations; import org.lilyproject.runtime.module.Module; import org.lilyproject.runtime.module.ModuleConfig; import org.lilyproject.runtime.module.build.ModuleBuilder; import org.lilyproject.runtime.module.build.ModuleConfigBuilder; import org.lilyproject.runtime.module.javaservice.JavaServiceManager; import org.lilyproject.runtime.rapi.ConfRegistry; import org.lilyproject.runtime.rapi.Mode; import org.lilyproject.runtime.repository.ArtifactNotFoundException; import org.lilyproject.runtime.repository.ArtifactRepository; import org.lilyproject.runtime.source.ModuleSourceManager; import org.lilyproject.util.ArgumentValidator; import org.lilyproject.util.Version; /** * This is the main entry point of the LilyRuntime. * * <p>Basic usage: * <ul> * <li>Create a {@link LilyRuntimeSettings} * <li>Use {@link #LilyRuntime(LilyRuntimeSettings)} * <li>Call {@link #start()} * </ul> */ public class LilyRuntime { private LilyRuntimeSettings settings; private LilyRuntimeModel model; private ClassLoader rootClassLoader; private List<Module> modules; private Map<String, Module> modulesById = new HashMap<String, Module>(); private List<ModuleConfig> moduleConfigs; private JavaServiceManager javaServiceManager; private ModuleSourceManager moduleSourceManager; private enum LifeCycle { NOT_STARTED, STARTED, STOPPED } private LifeCycle state = LifeCycle.NOT_STARTED; private Mode mode = Mode.getDefault(); private FilesystemAlterationMonitor fam = new FilesystemAlterationMonitor(); protected final Log infolog = LogFactory.getLog(INFO_LOG_CATEGORY); public static final String INFO_LOG_CATEGORY = "org.lilyproject.runtime.info"; public static final String CLASSLOADING_LOG_CATEGORY = "org.lilyproject.runtime.classloading-info"; public static final String CLASSLOADING_REPORT_CATEGORY = "org.lilyproject.runtime.classloading-report"; public LilyRuntime(LilyRuntimeSettings settings) { ArgumentValidator.notNull(settings, "settings"); if (settings.getRepository() == null) { throw new LilyRTException("LilyRuntimeSettings should contain an artifact repository."); } if (settings.getConfManager() == null) { throw new LilyRTException("LilyRuntimeSettings should contain a ConfManager."); } this.settings = settings; this.javaServiceManager = new JavaServiceManager(); this.moduleSourceManager = new ModuleSourceManager(fam); } public Mode getMode() { return mode; } public void setMode(Mode mode) { if (state.ordinal() > LifeCycle.NOT_STARTED.ordinal()) { throw new IllegalStateException("Runtime mode canot be changed once started."); } this.mode = mode; } public LilyRuntimeModel buildModel() { // Init the configuration manager ConfManager confManager = settings.getConfManager(); confManager.initRuntimeConfig(); ConfRegistry confRegistry = confManager.getRuntimeConfRegistry(); return buildModel(confRegistry); } private LilyRuntimeModel buildModel(ConfRegistry confRegistry) { LilyRuntimeModel newModel; if ((settings.getModel() != null)) { newModel = settings.getModel(); } else { Conf modulesConf = confRegistry.getConfiguration("wiring", false, false); Set<String> disabledModuleIds = settings.getDisabledModuleIds() != null ? settings.getDisabledModuleIds() : Collections.<String>emptySet(); SourceLocations sourceLocations = settings.getSourceLocations() != null ? settings.getSourceLocations() : new SourceLocations(); try { newModel = LilyRuntimeModelBuilder.build(modulesConf, disabledModuleIds, settings.getRepository(), sourceLocations); } catch (Exception e) { throw new LilyRTException("Error building the Lily model from configuration.", e); } } return newModel; } /** * Starts the Lily Runtime. This will launch all modules (i.e. their Spring containers), * and set up the restservices. * * <p>A LilyRuntime instance can only be started once, even after it has been stopped. * Just create a new LilyRuntime instance with the same LilyRuntimeConfig if you want * to (re)start another instance. */ public void start() throws LilyRTException, MalformedURLException, ArtifactNotFoundException { // a LilyRuntime object cannot be started twice, even if it has been stopped in between if (state.ordinal() > LifeCycle.NOT_STARTED.ordinal()) { throw new LilyRTException("This Lily Runtime instance has already been started before."); } state = LifeCycle.STARTED; // Init the configuration manager ConfManager confManager = settings.getConfManager(); confManager.initRuntimeConfig(); ConfRegistry confRegistry = confManager.getRuntimeConfRegistry(); this.model = buildModel(confRegistry); // Validate the config List<ConfigError> configErrors = new ArrayList<ConfigError>(); model.validate(configErrors); if (configErrors.size() > 0) { StringBuilder errorMsg = new StringBuilder(); String subject = configErrors.size() == 1 ? "error" : "errors"; errorMsg.append("Encountered the following ").append(subject).append(" in the runtime configuration: "); for (int i = 0; i < configErrors.size(); i++) { if (i > 0) { errorMsg.append(", "); } errorMsg.append(configErrors.get(i).getMessage()); } throw new LilyRTException(errorMsg.toString()); } moduleConfigs = new ArrayList<ModuleConfig>(); // First read the configuration of each module, and do some classpath checks if (infolog.isInfoEnabled()) { infolog.info("Reading module configurations of " + model.getModules().size() + " modules."); } for (ModuleDefinition entry : model.getModules()) { if (infolog.isInfoEnabled()) { infolog.debug("Reading module config " + entry.getId() + " - " + entry.getFile().getAbsolutePath()); } ModuleConfig moduleConf = ModuleConfigBuilder.build(entry, this); moduleConfigs.add(moduleConf); } // Check / build class path configurations Conf classLoadingConf = confRegistry.getConfiguration("classloading"); List<ClasspathEntry> sharedClasspath = ClassLoaderConfigurer.configureClassPaths(moduleConfigs, settings.getEnableArtifactSharing(), classLoadingConf); // Construct the shared classloader infolog.debug("Creating shared classloader"); rootClassLoader = ClassLoaderBuilder.build(sharedClasspath, this.getClass().getClassLoader(), settings.getRepository()); // Construct the classloaders of the various modules List<ClassLoader> moduleClassLoaders = new ArrayList<ClassLoader>(); for (ModuleConfig cfg : moduleConfigs) { ClassLoader classLoader = cfg.getClassLoadingConfig().getClassLoader(getClassLoader()); moduleClassLoaders.add(classLoader); } // Initialize the ConfManager for the modules configuration confManager.initModulesConfig(moduleConfigs); // Create the modules infolog.info("Starting the modules."); modules = new ArrayList<Module>(model.getModules().size()); for (int i = 0; i < moduleConfigs.size(); i++) { ModuleConfig moduleConfig = moduleConfigs.get(i); Module module = ModuleBuilder.build(moduleConfig, moduleClassLoaders.get(i), this); modules.add(module); modulesById.put(module.getDefinition().getId(), module); } // Start the FAM, conf manager refreshing fam.start(); confManager.startRefreshing(); infolog.info("Runtime initialisation finished."); } public List<ModuleConfig> getModuleConfigs() { return moduleConfigs; } public ArtifactRepository getArtifactRepository() { return settings.getRepository(); } public ClassLoader getClassLoader() { return rootClassLoader; } public JavaServiceManager getJavaServiceManager() { return javaServiceManager; } public ModuleSourceManager getModuleSourceManager() { return moduleSourceManager; } public List<Module> getModules() { return modules; } public Module getModuleById(String moduleId) { return modulesById.get(moduleId); } public LilyRuntimeSettings getSettings() { return settings; } public LilyRuntimeModel getModel() { return model; } public void stop() { if (state != LifeCycle.STARTED) { throw new LilyRTException("Cannot stop the runtime, it is in state " + state + " instead of " + LifeCycle.STARTED); } // TODO temporarily disabled because FAM.stop() is slow // See JCI jira patch: https://issues.apache.org/jira/browse/JCI-57 (the newer one in commons-io has the same problem) // fam.stop(); // Added the following workaround: if (System.getSecurityManager() == null) { try { Field famRunningField = fam.getClass().getDeclaredField("running"); famRunningField.setAccessible(true); Field threadField = fam.getClass().getDeclaredField("thread"); threadField.setAccessible(true); Thread famThread = (Thread)threadField.get(fam); if (famThread != null) { famRunningField.setBoolean(fam, false); famThread.interrupt(); fam.stop(); } } catch (Exception e) { infolog.error("Error stopping FilesystemAlterationMonitor", e); } } else { infolog.warn("Unable to stop the FilesystemAlterationMonitor using workaround since a security manager is installed."); } // In case starting the runtime failed, modules might be null if (modules != null) { infolog.info("Shutting down the modules."); List<Module> reversedModules = new ArrayList<Module>(this.modules); Collections.reverse(reversedModules); this.modules = null; this.javaServiceManager.stop(); for (Module module : reversedModules) { try { module.shutdown(); } catch (Throwable t) { infolog.error("Error shutting down module " + module.getDefinition().getId(), t); } } } settings.getConfManager().shutdown(); } public static String getVersion() { return Version.readVersion("org.lilyproject", "lily-runtime"); } public ConfManager getConfManager() { return settings.getConfManager(); } }