/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.core.runtime.internal; import com.bc.ceres.core.Assert; import com.bc.ceres.core.CoreException; import com.bc.ceres.core.ExtensibleObject; import com.bc.ceres.core.ProgressMonitor; import com.bc.ceres.core.SubProgressMonitor; import com.bc.ceres.core.runtime.Module; import com.bc.ceres.core.runtime.ModuleRuntime; import com.bc.ceres.core.runtime.ModuleState; import com.bc.ceres.core.runtime.ProxyConfig; import com.bc.ceres.core.runtime.RuntimeConfig; import com.bc.ceres.core.runtime.RuntimeRunnable; import java.io.File; import java.io.IOException; import java.net.URL; import java.security.CodeSource; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import static com.bc.ceres.core.runtime.Constants.SYSTEM_MODULE_NAME; // todo - handle "note: executing foreign code here!" // todo - delegate specific behaviour to kind-of RuntimeAdvisor / RuntimeConfigurer // todo - propagate or provide errors/warnings detected during start-up to client public class RuntimeImpl extends ExtensibleObject implements ModuleRuntime { public static final String UNINSTALL_FILE_SUFFIX = ".uninstall"; private final RuntimeConfig config; private final String[] commandLineArgs; private final ProgressMonitor progressMonitor; private ModuleImpl systemModule; private ModuleRegistry moduleRegistry; private ArrayList<ModuleImpl> resolvedModules; private boolean running; private long lastModuleId = 0L; public RuntimeImpl(RuntimeConfig config, String[] commandLineArgs, ProgressMonitor progressMonitor) { Assert.notNull(config, "config"); Assert.notNull(commandLineArgs, "commandLineArgs"); Assert.notNull(progressMonitor, "progressMonitor"); this.config = config; this.commandLineArgs = commandLineArgs; this.progressMonitor = progressMonitor; } public RuntimeConfig getRuntimeConfig() { return config; } public String[] getCommandLineArgs() { return commandLineArgs; } public Logger getLogger() { return config.getLogger(); } public Module getModule() { return systemModule; } public Module getModule(long id) { return moduleRegistry.getModule(id); } public Module[] getModules() { return moduleRegistry.getModules(); } public Module installModule(URL url, ProxyConfig proxyConfig, ProgressMonitor pm) throws CoreException { String modulesDirPath = config.getModulesDirPath(); if (modulesDirPath == null) { throw new CoreException("Modules directory not set"); } File modulesDir = new File(modulesDirPath); ModuleInstaller moduleInstaller = new ModuleInstaller(getLogger()); ModuleImpl module = moduleInstaller.installModule(url, proxyConfig, modulesDir, pm); registerModule(module, newModuleId()); return module; } public void start() throws CoreException { if (running) { throw new CoreException("Already running"); } running = true; progressMonitor.beginTask("Starting runtime", 100); getLogger().info(MessageFormat.format("Starting runtime for context [{0}]...", getContextId())); logRuntimeConfig(); try { progressMonitor.setSubTaskName("Loading modules"); loadModules(SubProgressMonitor.create(progressMonitor, 10)); initSystemModule(); progressMonitor.worked(5); // = 5% progressMonitor.setSubTaskName("Resolving modules"); resolveModules(SubProgressMonitor.create(progressMonitor, 5)); // = 15% progressMonitor.setSubTaskName("Starting modules"); startModules(SubProgressMonitor.create(progressMonitor, 55)); // = 70% registerShutdownHook(); progressMonitor.setSubTaskName("Running application"); runApplication(SubProgressMonitor.create(progressMonitor, 30)); // = 100% } finally { progressMonitor.done(); } } public void stop() throws CoreException { if (!running) { return; } getLogger().info(MessageFormat.format("Stopping runtime for context [{0}]...", getContextId())); stopModules(); dispose(); running = false; } private String getContextId() { return config.getContextId(); } private void dispose() { systemModule = null; moduleRegistry = null; resolvedModules = null; } private void logRuntimeConfig() { getLogger().info("Runtime configuration:"); getLogger().info(String.format(" contextId = %s", config.getContextId())); getLogger().info(String.format(" homeDirPath = %s", config.getHomeDirPath())); getLogger().info(String.format(" configFilePath = %s", config.getConfigFilePath())); getLogger().info(String.format(" modulesDirPath = %s", config.getModulesDirPath())); String[] libDirPaths = config.getLibDirPaths(); for (int i = 0; i < libDirPaths.length; i++) { getLogger().info(String.format(" libDirPaths.%d = %s", i, config.getLibDirPaths()[i])); } getLogger().info(String.format(" mainClassName = %s", config.getMainClassName())); getLogger().info(String.format(" applicationId = %s", config.getApplicationId())); } private void loadModules(ProgressMonitor pm) throws CoreException { pm.beginTask("Loading modules", 3); try { if (config.getModulesDirPath() != null) { uninstallModules(SubProgressMonitor.create(pm, 1)); } else { pm.worked(1); } moduleRegistry = new ModuleRegistry(); ModuleLoader moduleLoader = new ModuleLoader(getLogger()); if (config.getModulesDirPath() != null) { loadModulesFromModulesDir(moduleLoader, SubProgressMonitor.create(pm, 1)); } else { pm.worked(1); } loadModulesFromClasspath(moduleLoader, SubProgressMonitor.create(pm, 1)); } finally { pm.done(); } } private void uninstallModules(ProgressMonitor pm) { ModuleUninstaller moduleUninstaller = new ModuleUninstaller(getLogger()); moduleUninstaller.uninstallModules(new File(config.getModulesDirPath()), pm); } private void loadModulesFromModulesDir(ModuleLoader moduleLoader, ProgressMonitor pm) throws CoreException { File modulesDir = new File(config.getModulesDirPath()); if (modulesDir.isDirectory()) { getLogger().info(String.format("Searching for modules in [%s]...", modulesDir.getPath())); try { ModuleImpl[] modules = moduleLoader.loadModules(modulesDir, pm); getLogger().info(String.format("%d module(s) found.", modules.length)); registerModules(modules); } catch (IOException e) { throw new CoreException(e); } } } private void loadModulesFromClasspath(ModuleLoader moduleLoader, ProgressMonitor pm) throws CoreException { try { getLogger().info("Searching for modules in classpath..."); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); ModuleImpl[] modules = moduleLoader.loadModules(contextClassLoader, pm); getLogger().info(String.format("%d module(s) found.", modules.length)); registerModules(modules); } catch (IOException e) { throw new CoreException(e); } } private void registerModules(ModuleImpl[] modules) { for (ModuleImpl module : modules) { try { if (moduleRegistry.getModule(module.getLocation()) != null) { getLogger().warning( MessageFormat.format("Module [{0}-{1}@{2}] already registered.", module.getSymbolicName(), module.getVersion(), module.getLocation())); } else { long id = module.getSymbolicName().equals(SYSTEM_MODULE_NAME) ? 0L : newModuleId(); if (moduleRegistry.getModule(id) == null) { // ceres-core is maybe already registered registerModule(module, id); getLogger().info(MessageFormat.format("Module [{0}-{1}] registered.", module.getSymbolicName(), module.getVersion())); } } } catch (CoreException e) { logError(MessageFormat.format("Failed to register module [{0}-{1}@{2}].", module.getSymbolicName(), module.getVersion(), module.getLocation()), e); } } } private long newModuleId() { return ++lastModuleId; } private void initSystemModule() throws CoreException { ModuleImpl[] systemModules = moduleRegistry.getModules(SYSTEM_MODULE_NAME); if (systemModules.length > 0) { systemModule = systemModules[0]; } if (systemModule == null) { URL location = getCodeSourceLocation(); ModuleReader moduleReader = new ModuleReader(getLogger()); systemModule = moduleReader.readFromLocation(location); registerModule(systemModule, 0L); } } private void registerModule(ModuleImpl module, long moduleId) throws CoreException { module.setRuntime(this); module.setModuleId(moduleId); moduleRegistry.registerModule(module); } private void resolveModules(ProgressMonitor pm) { ModuleImpl[] modules = moduleRegistry.getModules(); resolvedModules = new ArrayList<ModuleImpl>(modules.length); pm.beginTask("Resolving modules", modules.length + 1); try { for (ModuleImpl module : modules) { resolveModule(module); pm.worked(1); } Collections.sort(resolvedModules, new Comparator<ModuleImpl>() { public int compare(ModuleImpl m1, ModuleImpl m2) { return m2.getRefCount() - m1.getRefCount(); } }); pm.worked(1); logResolveSummary(); } finally { pm.done(); } } private void resolveModule(ModuleImpl module) { getLogger().info(MessageFormat.format("Resolving module [{0}-{1}].", module.getSymbolicName(), module.getVersion())); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); final ModuleResolver moduleResolver = new ModuleResolver(contextClassLoader, true); try { moduleResolver.resolve(module); systemModule.setRefCount(systemModule.getRefCount() + module.getRefCount()); resolvedModules.add(module); } catch (ResolveException e) { logError(e.getMessage(), e); } List<ResolveException> resolveErrors = Arrays.asList(module.getResolveErrors()); if (!resolveErrors.isEmpty()) { Collections.reverse(resolveErrors); for (ResolveException resolveError : resolveErrors) { getLogger().log(Level.SEVERE, resolveError.getMessage()); } } List<ResolveException> resolveWarnings = Arrays.asList(module.getResolveWarnings()); if (!resolveWarnings.isEmpty()) { Collections.reverse(resolveWarnings); for (ResolveException resolveWarning : resolveWarnings) { getLogger().log(Level.WARNING, resolveWarning.getMessage()); } } } private void logResolveSummary() { for (ModuleImpl module : resolvedModules) { String msg = MessageFormat.format("Module [{0}-{1}] resolved, reference count is {2}.", module.getSymbolicName(), module.getVersion(), module.getRefCount()); getLogger().fine(msg); // Note for all resolvedModules, always module.moduleDependencies != null, ModuleImpl[] moduleDependencies = module.getModuleDependencies(); for (int i = 0; i < moduleDependencies.length; i++) { msg = MessageFormat.format(" moduleDependencies[{0}] = {1}", i, moduleDependencies[i].getSymbolicName()); getLogger().fine(msg); } ModuleClassLoader classLoader = (ModuleClassLoader) module.getClassLoader(); msg = MessageFormat.format(" classLoader = {0}", classLoader); getLogger().fine(msg); URL[] urls = classLoader.getURLs(); for (int i = 0; i < urls.length; i++) { msg = MessageFormat.format(" classLoader.urls[{0}] = {1}", i, urls[i]); getLogger().fine(msg); } URL[] nativeUrls = classLoader.getNativeUrls(); for (int i = 0; i < nativeUrls.length; i++) { msg = MessageFormat.format(" classLoader.nativeUrls[{0}] = {1}", i, nativeUrls[i]); getLogger().fine(msg); } ClassLoader[] delegates = classLoader.getDelegates(); for (int i = 0; i < delegates.length; i++) { msg = MessageFormat.format(" classLoader.delegates[{0}] = {1}", i, delegates[i]); getLogger().fine(msg); } } } private void startModules(ProgressMonitor subProgressMonitor) { try { subProgressMonitor.beginTask("Starting modules", resolvedModules.size()); for (ModuleImpl module : resolvedModules) { if (module.getState() == ModuleState.RESOLVED) { try { subProgressMonitor.setSubTaskName(module.getName()); module.start(); getLogger().info(MessageFormat.format("Module [{0}-{1}] started.", module.getSymbolicName(), module.getVersion())); } catch (CoreException e) { logError(MessageFormat.format("Failed to start module [{0}-{1}].", module.getSymbolicName(), module.getVersion()), e); } } subProgressMonitor.worked(1); } } finally { subProgressMonitor.done(); } } private void stopModules() { for (int i = resolvedModules.size() - 1; i >= 0; i--) { ModuleImpl module = resolvedModules.get(i); if (module.getState() == ModuleState.ACTIVE) { try { module.stop(); getLogger().info(MessageFormat.format("Module [{0}-{1}] stopped.", module.getSymbolicName(), module.getVersion())); } catch (CoreException e) { logError(MessageFormat.format("Failed to stop module [{0}-{1}].", module.getSymbolicName(), module.getVersion()), e); } } } } private void registerShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { RuntimeImpl.this.stop(); } catch (CoreException e) { logError("Failed to shutdown runtime.", e); } } }); getLogger().info("Shutdown hook registered."); } private void runApplication(ProgressMonitor pm) throws CoreException { String applicationId = config.getApplicationId(); if (applicationId == null) { return; } RuntimeRunnable application = getRuntimeActivator().getApplication(applicationId); if (application == null) { throw new CoreException(MessageFormat.format("Application [{0}] not found", applicationId)); } try { getLogger().info(MessageFormat.format("Invoking application [{0}].", applicationId)); // note: executing foreign code here! application.run(commandLineArgs, pm); getLogger().info(MessageFormat.format("Application [{0}] invoked.", applicationId)); } catch (Throwable t) { throw new CoreException(MessageFormat.format("Failed to invoke application [{0}]", applicationId), t); } } private RuntimeActivator getRuntimeActivator() { return ((RuntimeActivator) systemModule.getActivator()); } private URL getCodeSourceLocation() throws CoreException { CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); if (codeSource == null) { throw new CoreException("No code source available for system module"); } return codeSource.getLocation(); } private void logError(String msg, Throwable e) { getLogger().log(Level.SEVERE, msg, e); } }