/* * Copyright 2012 The Solmix Project * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.gnu.org/licenses/ * or see the FSF site: http://www.fsf.org. */ package org.solmix.launch.base.internal; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.launch.Framework; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.startlevel.FrameworkStartLevel; import org.solmix.launch.base.shared.LaunchConstants; import org.solmix.launch.base.shared.Notifiable; /** * * @author ffz * @version 2012-3-17 * @since 0.0.4 */ public class Solmix { public static final String PROP_SYSTEM_PACKAGES = "org.solmix.launch.system.packages"; public static final String PROP_SOLMIX_HOME = LaunchConstants.SOLMIX_HOME; public static final String PROP_ENV_SOLMIX_HOME = "SOLMIX_HOME"; public static final String PROP_SOLMIX_BASE = "solmix.base"; public static final String PROP_SOLMIX_DATA = "solmix.data"; private static final String JAAS_PROPERTYNAME = "java.security.auth.login.config"; /** * The property for auto-discovering the bundles */ public static final String PROP_AUTO_START = "auto.start"; public static final String PROP_FRAMEWORK_FELIX = "framework.felix"; public static final String FELIX_CONFIG_PROPERTIES_FILE = "config.properties"; public static final String FELIX_SYSTEM_PROPERTIES_FILE = "system.properties"; public static final String FELIX_STARTUP_PROPERTIES_FILE = "startup.properties"; public static final String PROP_DEFAULT_REPOSITORY = "default.repository"; public static final String INCLUDES_PROPERTY = "${includes}"; // mandatory includes public static final String OPTIONALS_PROPERTY = "${optionals}"; // optionals includes private static final String DELIM_START = "${"; private static final String DELIM_STOP = "}"; private File solmixHome; private File solmixBase; private File solmixData; private Properties configProps; private final int shutdownTimeout = 5 * 60 * 1000; Logger LOG = Logger.getLogger(this.getClass().getName()); /** * The <code>Felix</code> instance loaded on {@link #init()} and stopped on {@link #destroy()}. */ private Framework framework; private final String PROP_OSGI_FRAMEWORK_FACTORY = "osgi.framework.factory"; private final String PROP_AUTO_INSTALL = "auto.install"; public Solmix(final Notifiable notifiable, final Map<String, String> propOverwrite) { try { launch(notifiable, propOverwrite); } catch (Exception e) { destroy(); e.printStackTrace(); LOG.log(Level.FINEST, "ErrorCode:-1\n can't start osgi framework!", e); } } /** * Set the JAAS system configuration file. */ protected void tryToSetJAASConfig() { String jaas = System.getProperty(JAAS_PROPERTYNAME); String home = System.getProperty(PROP_SOLMIX_HOME); if (home == null) home = ""; if (jaas == null || jaas.length() == 0) { try { System.setProperty(JAAS_PROPERTYNAME, home + System.getProperty("file.separator") + "etc" + System.getProperty("file.separator") + "jaas.config"); } catch (SecurityException se) { LOG.log(Level.FINER, "Failed to set {" + JAAS_PROPERTYNAME + "}, check application server settings"); LOG.log(Level.FINER, se.getMessage()); LOG.log(Level.FINER, "Aborting startup"); return; } } else { LOG.info("JAAS config file set by parent container or some other application"); LOG.info("Config in use {" + System.getProperty(JAAS_PROPERTYNAME) + "}"); LOG.info("Please make sure JAAS config has all necessary modules (refer config/jaas.config) configured"); } } /** * 加载config.properties 文件中的配置 * * @param propOverwrite * @return * @throws FileNotFoundException */ private Properties loadConfigProperties(Map<String, String> propOverwrite) throws Exception { // See if the property URL was specified as a property. URL configPropURL; try { File etcFolder = new File(solmixBase, "etc"); System.out.println(solmixBase.getAbsolutePath()); if (!etcFolder.exists()) { throw new FileNotFoundException("etc folder not found: " + etcFolder.getAbsolutePath()); } File file = new File(etcFolder, FELIX_CONFIG_PROPERTIES_FILE); configPropURL = file.toURI().toURL(); } catch (MalformedURLException ex) { System.err.print("Main: " + ex); return null; } Properties configProps = loadPropertiesFile(configPropURL, false); configProps.putAll(propOverwrite); // Perform variable substitution for system properties. for (Enumeration e = configProps.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); configProps.setProperty(name, substVars(configProps.getProperty(name), name, null, configProps)); } return configProps; } protected static Properties loadPropertiesFile(URL configPropURL, boolean failIfNotFound) throws Exception { // Read the properties file. Properties configProps = new Properties(); InputStream is = null; try { is = configPropURL.openConnection().getInputStream(); configProps.load(is); is.close(); } catch (FileNotFoundException ex) { if (failIfNotFound) { throw ex; } else { System.err.println("WARN: " + configPropURL + " is not found, so not loaded"); } } catch (Exception ex) { System.err.println("Error loading config properties from " + configPropURL); System.err.println("Main: " + ex); return configProps; } finally { try { if (is != null) { is.close(); } } catch (IOException ex2) { // Nothing we can do. } } String includes = configProps.getProperty(INCLUDES_PROPERTY); if (includes != null) { StringTokenizer st = new StringTokenizer(includes, "\" ", true); if (st.countTokens() > 0) { String location; do { location = nextLocation(st); if (location != null) { URL url = new URL(configPropURL, location); Properties props = loadPropertiesFile(url, true); configProps.putAll(props); } } while (location != null); } configProps.remove(INCLUDES_PROPERTY); } String optionals = configProps.getProperty(OPTIONALS_PROPERTY); if (optionals != null) { StringTokenizer st = new StringTokenizer(optionals, "\" ", true); if (st.countTokens() > 0) { String location; do { location = nextLocation(st); if (location != null) { URL url = new URL(configPropURL, location); Properties props = loadPropertiesFile(url, false); configProps.putAll(props); } } while (location != null); } configProps.remove(OPTIONALS_PROPERTY); } for (Enumeration e = configProps.propertyNames(); e.hasMoreElements();) { Object key = e.nextElement(); if (key instanceof String) { String v = configProps.getProperty((String) key); configProps.put(key, v.trim()); } } return configProps; } private static String nextLocation(StringTokenizer st) { String retVal = null; if (st.countTokens() > 0) { String tokenList = "\" "; StringBuffer tokBuf = new StringBuffer(10); String tok; boolean inQuote = false; boolean tokStarted = false; boolean exit = false; while ((st.hasMoreTokens()) && (!exit)) { tok = st.nextToken(tokenList); if (tok.equals("\"")) { inQuote = !inQuote; if (inQuote) { tokenList = "\""; } else { tokenList = "\" "; } } else if (tok.equals(" ")) { if (tokStarted) { retVal = tokBuf.toString(); tokStarted = false; tokBuf = new StringBuffer(10); exit = true; } } else { tokStarted = true; tokBuf.append(tok.trim()); } } // Handle case where end of token stream and // still got data if ((!exit) && (tokStarted)) { retVal = tokBuf.toString(); } } return retVal; } private Framework createFramework(final Notifiable notifiable, Properties props) throws Exception { OsgiFrameworkFactory factory = new OsgiFrameworkFactoryImpl(); return factory.newFramework(notifiable, props); } public void launch(final Notifiable notifiable, final Map<String, String> propOverwrite) throws Exception { if (propOverwrite.get(PROP_SOLMIX_HOME) != null) { File home = new File(propOverwrite.get(PROP_SOLMIX_HOME).trim()); if (home.exists() && home.isDirectory()) { solmixHome = home; } else { solmixHome = Utils.getSolmixHome(Solmix.class, PROP_SOLMIX_HOME, PROP_ENV_SOLMIX_HOME); } if (solmixHome == null) throw new IOException("solmix home must set to start framework"); } solmixBase = Utils.getDirectory(PROP_SOLMIX_BASE, solmixHome, false, true); solmixData = Utils.getDirectory(PROP_SOLMIX_DATA, new File(solmixBase, "data"), false, true); System.setProperty(PROP_SOLMIX_HOME, solmixHome.getAbsolutePath()); System.setProperty(PROP_SOLMIX_BASE, solmixBase.getAbsolutePath()); System.setProperty(PROP_SOLMIX_DATA, solmixData.getAbsolutePath()); tryToSetJAASConfig(); // 加载系统配置,配置参数放入system.setProperty() loadSystemProperties(solmixBase); // read the default parameters configProps = this.loadConfigProperties(propOverwrite); BootstrapLogManager.setProperties(configProps); LOG.addHandler(BootstrapLogManager.getDefaultHandler()); // ClassLoader classLoader = createClassLoader(configProps); if (configProps.getProperty(Constants.FRAMEWORK_STORAGE) == null) { File storage = new File(solmixData.getPath(), "cache"); try { storage.mkdirs(); } catch (SecurityException se) { throw new Exception(se.getMessage()); } configProps.setProperty(Constants.FRAMEWORK_STORAGE, storage.getAbsolutePath()); } // String factoryClass = configProps.getProperty(PROP_OSGI_FRAMEWORK_FACTORY); // if (factoryClass == null) { // InputStream is = classLoader.getResourceAsStream("META-INF/services/" + FrameworkFactory.class.getName()); // BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); // factoryClass = br.readLine(); // br.close(); // } // FrameworkFactory factory = (FrameworkFactory) classLoader.loadClass(factoryClass).newInstance(); framework = createFramework(notifiable, configProps); framework.init(); loadStartupProperties(configProps); processBundlesInstall(framework.getBundleContext()); framework.start(); } /** * @param bundleContext */ private void processBundlesInstall(BundleContext bundleContext) { // Check if we want maven style URL boolean mvnStyle = Boolean.parseBoolean(configProps.getProperty("mvn.style", "true")); // Retrieve the Start Level service, since it will be needed // to set the start level of the installed bundles. FrameworkStartLevel startLevel = framework.adapt(FrameworkStartLevel.class); // set auto start level is 60. startLevel.setInitialBundleStartLevel(60); // if frameworkk is clean and install all bundles in it. if (framework.getBundleContext().getBundles().length == 1) { autoInstall(PROP_AUTO_INSTALL, bundleContext, startLevel, mvnStyle, false); autoInstall(PROP_AUTO_START, bundleContext, startLevel, mvnStyle, true); } } /** * @param pROP_AUTO_INSTALL2 * @param bundleContext * @param startLevel * @param mvnStyle * @param b */ private List<Bundle> autoInstall(String propertyPrefix, BundleContext context, FrameworkStartLevel fsl, boolean mvnStyle, boolean start) { Map<Integer, String> autoStart = new TreeMap<Integer, String>(); List<Bundle> bundles = new ArrayList<Bundle>(); for (Object o : configProps.keySet()) { String key = (String) o; // Ignore all keys that are not the auto-start property. if (!key.startsWith(propertyPrefix)) { continue; } // If the auto-start property does not have a start level, // then assume it is the default bundle start level, otherwise // parse the specified start level. int startLevel = fsl.getInitialBundleStartLevel(); if (!key.equals(propertyPrefix)) { try { startLevel = Integer.parseInt(key.substring(key.lastIndexOf('.') + 1)); } catch (NumberFormatException ex) { System.err.println("Invalid property: " + key); } } autoStart.put(startLevel, configProps.getProperty(key)); } for (Integer startLevel : autoStart.keySet()) { StringTokenizer st = new StringTokenizer(autoStart.get(startLevel), "\" ", true); if (st.countTokens() > 0) { String location; do { location = nextLocation(st); if (location != null) { try { String[] parts = Utils.convertToMavenUrlsIfNeeded(location, mvnStyle); Bundle b = context.installBundle(parts[0], new URL(parts[1]).openStream()); b.adapt(BundleStartLevel.class).setStartLevel(startLevel); bundles.add(b); } catch (Exception ex) { System.err.println("Error installing bundle " + location + ": " + ex); } } } while (location != null); } } // Now loop through and start the installed bundles. if (start) { for (Bundle b : bundles) { try { String fragmentHostHeader = b.getHeaders().get(Constants.FRAGMENT_HOST); if (fragmentHostHeader == null || fragmentHostHeader.trim().length() == 0) { b.start(); } } catch (Exception ex) { System.err.println("Error starting bundle " + b.getSymbolicName() + ": " + ex); } } } return bundles; } /** * @param configProps2 */ private void loadStartupProperties(Properties configProps) throws Exception { /** * load the startup bundles jar File. */ List<File> bundleDirs = new ArrayList<File>(); URL startupPropURL = null; File etcFolder = new File(solmixBase, "etc"); if (!etcFolder.exists()) { throw new FileNotFoundException("etc folder not found: " + etcFolder.getAbsolutePath()); } File file = new File(etcFolder, FELIX_STARTUP_PROPERTIES_FILE); startupPropURL = file.toURI().toURL(); Properties startupProps = loadPropertiesFile(startupPropURL, true); String defaultRepo = System.getProperty(PROP_DEFAULT_REPOSITORY, "bundles"); if (solmixBase.equals(solmixHome)) { File systemRepo = new File(solmixHome, defaultRepo); if (!systemRepo.exists()) { throw new FileNotFoundException("system repo not found: " + systemRepo.getAbsolutePath()); } bundleDirs.add(systemRepo); } else { File baseSystemRepo = new File(solmixBase, defaultRepo); File homeSystemRepo = new File(solmixHome, defaultRepo); if (!baseSystemRepo.exists() && !homeSystemRepo.exists()) { throw new FileNotFoundException("system repos not found: " + baseSystemRepo.getAbsolutePath() + " " + homeSystemRepo.getAbsolutePath()); } bundleDirs.add(baseSystemRepo); bundleDirs.add(homeSystemRepo); } processConfigProps(configProps, startupProps, bundleDirs); } /** * @param configProps * @param startupProps * @param bundleDirs * @throws Exception */ private void processConfigProps(Properties configProps, Properties startupProps, List<File> bundleDirs) throws Exception { if (bundleDirs == null) { return; } boolean hasErrors = false; String autoStartFlag = configProps.getProperty(PROP_AUTO_START, "").trim(); configProps.remove(PROP_AUTO_START); if ("all".equals(autoStartFlag)) { ArrayList<File> jars = new ArrayList<File>(); for (File bundleDir : bundleDirs) { findJars(bundleDir, jars); } StringBuffer sb = new StringBuffer(); for (File jar : jars) { try { sb.append("\"").append(jar.toURI().toURL().toString()).append("\" "); } catch (MalformedURLException e) { System.err.print("Ignoring " + jar.toString() + " (" + e + ")"); } } configProps.setProperty(PROP_AUTO_START, sb.toString()); } else if (FELIX_STARTUP_PROPERTIES_FILE.equals(autoStartFlag)) { HashMap<Integer, StringBuffer> levels = new HashMap<Integer, StringBuffer>(); for (Object o : startupProps.keySet()) { String name = (String) o; File file = findFile(bundleDirs, name); if (file != null) { Integer level; try { level = new Integer(startupProps.getProperty(name).trim()); } catch (NumberFormatException e1) { System.err.print("Ignoring " + file.toString() + " (run level must be an integer)"); continue; } StringBuffer sb = levels.get(level); if (sb == null) { sb = new StringBuffer(256); levels.put(level, sb); } try { sb.append("\"").append(file.toURI().toURL().toString()).append("|").append(name).append("\" "); } catch (MalformedURLException e) { System.err.print("Ignoring " + file.toString() + " (" + e + ")"); } } else { System.err.println("Bundle listed in " + FELIX_STARTUP_PROPERTIES_FILE + " configuration not found: " + name); hasErrors = true; } } for (Map.Entry<Integer, StringBuffer> entry : levels.entrySet()) { configProps.setProperty(PROP_AUTO_START + "." + entry.getKey(), entry.getValue().toString()); } } if (hasErrors) { throw new Exception("Aborting due to missing startup bundles"); } } private static File findFile(List<File> bundleDirs, String name) { for (File bundleDir : bundleDirs) { File file = findFile(bundleDir, name); if (file != null) { return file; } } return null; } private static File findFile(File dir, String name) { name = fromMaven(name); File theFile = new File(dir, name); if (theFile.exists() && !theFile.isDirectory()) { return theFile; } return null; } private static final Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?"); /** * Returns a path for an srtifact. Input: path (no ':') returns path Input: * mvn:<groupId>/<artifactId>/<version>/<type>/<classifier> converts to default repo location path type and * classifier are optional. * * * @param name input artifact info * @return path as supplied or a default maven repo path */ static String fromMaven(String name) { Matcher m = mvnPattern.matcher(name); if (!m.matches()) { return name; } StringBuilder b = new StringBuilder(); b.append(m.group(1)); for (int i = 0; i < b.length(); i++) { if (b.charAt(i) == '.') { b.setCharAt(i, '/'); } } b.append("/");// groupId String artifactId = m.group(2); String version = m.group(3); String extension = m.group(5); String classifier = m.group(7); b.append(artifactId).append("/");// artifactId b.append(version).append("/");// version b.append(artifactId).append("-").append(version); if (present(classifier)) { b.append("-").append(classifier); } else { if (present(extension)) { b.append(".").append(extension); } else { b.append(".jar"); } } return b.toString(); } private static boolean present(String part) { return part != null && !part.isEmpty(); } private static void findJars(File dir, ArrayList<File> jars) { for (File file : dir.listFiles()) { if (file.isDirectory()) { findJars(file, jars); } else { if (file.toString().endsWith(".jar")) { jars.add(file); } } } } /** * @param configProps * @return * @throws Exception */ private ClassLoader createClassLoader(Properties configProps) throws Exception { String bundle = configProps.getProperty(PROP_FRAMEWORK_FELIX); if (bundle == null) { throw new IllegalArgumentException("Property " + PROP_FRAMEWORK_FELIX + " must be set in the etc/" + FELIX_CONFIG_PROPERTIES_FILE + " configuration file"); } File bundleFile = new File(solmixBase, bundle); if (!bundleFile.exists()) { bundleFile = new File(solmixHome, bundle); } if (!bundleFile.exists()) { throw new FileNotFoundException(bundleFile.getAbsolutePath()); } List<URL> urls = new ArrayList<URL>(); urls.add(bundleFile.toURI().toURL()); // File[] libs = new File(solmixHome, "lib").listFiles(); // if (libs != null) { // for (File f : libs) { // if (f.isFile() && f.canRead() && f.getName().endsWith(".jar")) { // urls.add(f.toURI().toURL()); // } // } // } return new URLClassLoader(urls.toArray(new URL[urls.size()]), Solmix.class.getClassLoader()); } /** * @param dir */ @SuppressWarnings("rawtypes") protected void loadSystemProperties(File dir) { URL propURL; try { File file = new File(new File(solmixBase, "etc"), FELIX_SYSTEM_PROPERTIES_FILE); propURL = file.toURI().toURL(); } catch (MalformedURLException ex) { System.err.print("Main: " + ex); return; } // Read the properties file. Properties props = new Properties(); InputStream is = null; try { is = propURL.openConnection().getInputStream(); props.load(is); is.close(); } catch (FileNotFoundException ex) { // Ignore file not found. } catch (Exception ex) { System.err.println("Main: Error loading system properties from " + propURL); System.err.println("Main: " + ex); try { if (is != null) is.close(); } catch (IOException ex2) { // Nothing we can do. } return; } // Perform variable substitution on specified properties. for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = System.getProperty(name, props.getProperty(name)); System.setProperty(name, substVars(value, name, null, props)); } } public static String substVars(String val, String currentKey, Map<String, String> cycleMap, Properties configProps) throws IllegalArgumentException { // If there is currently no cycle map, then create // one for detecting cycles for this invocation. if (cycleMap == null) { cycleMap = new HashMap<String, String>(); } // Put the current key in the cycle map. cycleMap.put(currentKey, currentKey); // Assume we have a value that is something like: // "leading ${foo.${bar}} middle ${baz} trailing" // Find the first ending '}' variable delimiter, which // will correspond to the first deepest nested variable // placeholder. int stopDelim = val.indexOf(DELIM_STOP); // Find the matching starting "${" variable delimiter // by looping until we find a start delimiter that is // greater than the stop delimiter we have found. int startDelim = val.indexOf(DELIM_START); while (stopDelim >= 0) { int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length()); if ((idx < 0) || (idx > stopDelim)) { break; } else if (idx < stopDelim) { startDelim = idx; } } // If we do not have a start or stop delimiter, then just // return the existing value. if ((startDelim < 0) && (stopDelim < 0)) { return val; } // At this point, we found a stop delimiter without a start, // so throw an exception. else if (((startDelim < 0) || (startDelim > stopDelim)) && (stopDelim >= 0)) { throw new IllegalArgumentException("stop delimiter with no start delimiter: " + val); } // At this point, we have found a variable placeholder so // we must perform a variable substitution on it. // Using the start and stop delimiter indices, extract // the first, deepest nested variable placeholder. String variable = val.substring(startDelim + DELIM_START.length(), stopDelim); // Verify that this is not a recursive variable reference. if (cycleMap.get(variable) != null) { throw new IllegalArgumentException("recursive variable reference: " + variable); } // Get the value of the deepest nested variable placeholder. // Try to configuration properties first. String substValue = (configProps != null) ? configProps.getProperty(variable, null) : null; if (substValue == null) { // Ignore unknown property values. substValue = System.getProperty(variable, ""); } // Remove the found variable from the cycle map, since // it may appear more than once in the value and we don't // want such situations to appear as a recursive reference. cycleMap.remove(variable); // Append the leading characters, the substituted value of // the variable, and the trailing characters to get the new // value. val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length(), val.length()); // Now perform substitution again, since there could still // be substitutions to make. val = substVars(val, currentKey, cycleMap, configProps); // Return the value. return val; } protected BundleContext getBundleContext() { return framework.getBundleContext(); } /** * Destroys this servlet by shutting down the OSGi framework and hence the delegatee servlet if one is set at all. */ public final boolean destroy() { if (framework == null) { return true; } final Framework myFramework; synchronized (this) { myFramework = framework; framework = null; } if (myFramework.getState() == Bundle.ACTIVE || myFramework.getState() == Bundle.STARTING) { new Thread() { @Override public void run() { try { myFramework.stop(); } catch (BundleException e) { LOG.log(Level.FINEST, "Error stopping solmix: " + e.getMessage()); } } }.start(); } try { myFramework.waitForStop(0); } catch (InterruptedException e) { LOG.log(Level.FINEST, "Failure initiating Framework Shutdown", e); return false; } LOG.log(Level.INFO, "solmix framework stopped"); return true; } }