| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.sling.launchpad.base.impl; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.management.ManagementFactory; |
| import java.net.URL; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Properties; |
| import java.util.SortedMap; |
| import java.util.StringTokenizer; |
| import java.util.TreeMap; |
| |
| import javax.management.Attribute; |
| import javax.management.AttributeList; |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| |
| import org.apache.felix.framework.Logger; |
| import org.apache.felix.framework.util.FelixConstants; |
| import org.apache.sling.launchpad.api.LaunchpadContentProvider; |
| import org.apache.sling.launchpad.base.shared.Notifiable; |
| import org.apache.sling.launchpad.base.shared.SharedConstants; |
| import org.apache.sling.launchpad.base.shared.Util; |
| 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.service.url.URLConstants; |
| import org.osgi.service.url.URLStreamHandlerService; |
| |
| /** |
| * The <code>Sling</code> serves as the starting point for Sling. |
| * <ul> |
| * <li>The {@link #Sling(Notifiable, Logger, LaunchpadContentProvider, Map)} method launches Apache |
| * <code>Felix</code> as the OSGi framework implementation we use. |
| * </ul> |
| * <p> |
| * <b>Launch Configuration</b> |
| * <p> |
| * The Apache <code>Felix</code> framework requires configuration parameters to |
| * be specified for startup. This servlet builds the list of parameters from |
| * three locations: |
| * <ol> |
| * <li>The <code>sling.properties</code> file is read from the servlet class |
| * path. This properties file contains default settings.</li> |
| * <li>Extensions of this servlet may provide additional properties to be loaded |
| * overwriting the {@link #loadPropertiesOverride(Map)} method. |
| * <li>Finally, web application init parameters are added to the properties and |
| * may overwrite existing properties of the same name(s). |
| * </ol> |
| * <p> |
| * After loading all properties, variable substitution takes place on the |
| * property values. A variable is indicated as <code>${<prop-name>}</code> |
| * where <code><prop-name></code> is the name of a system or configuration |
| * property (configuration properties override system properties). Variables may |
| * be nested and are resolved from inner-most to outer-most. For example, the |
| * property value <code>${outer-${inner}}</code> is resolved by first resolving |
| * <code>${inner}</code> and then resolving the property whose name is the |
| * catenation of <code>outer-</code> and the result of resolving |
| * <code>${inner}</code>. |
| * <p> |
| */ |
| public class Sling { |
| |
| /** |
| * The name of the configuration property defining the Sling home directory |
| * as an URL (value is "sling.home.url"). |
| * <p> |
| * The value of this property is assigned the value of |
| * <code>new File(${sling.home}).toURI().toString()</code> before |
| * resolving the property variables. |
| * |
| * @see SharedConstants#SLING_HOME |
| */ |
| public static final String SLING_HOME_URL = "sling.home.url"; |
| |
| /** |
| * The name of the configuration property defining the JCR home directory |
| * (value is "sling.repository.home"). |
| * <p> |
| * The value of this property could be set as a system property, init-param in |
| * web.xml or property in sling.properties. |
| * <p> |
| * Default value to #SLING_HOME/repository_name |
| */ |
| public static final String JCR_REPO_HOME = "sling.repository.home"; |
| |
| /** |
| * The name of the configuration property defining the URL of an existing |
| * repository config file (repository.xml). |
| * <p> |
| * The value of this property could be set as a system property, init-param in |
| * web.xml or property in sling.properties. |
| * <p> |
| * Default value to #SLING_HOME/repository_name/repository.xml |
| */ |
| public static final String JCR_REPO_CONFIG_FILE_URL = "sling.repository.config.file.url"; |
| |
| /** |
| * The name of the configuration property defining a properties file |
| * defining a list of bundles, which are installed into the framework when |
| * it has been launched (value is "org.apache.osgi.bundles"). |
| * <p> |
| * This configuration property is generally set in the web application |
| * configuration and may be referenced in all property files (default, user |
| * supplied and web application parameters) used to build the framework |
| * configuration. |
| */ |
| public static final String OSGI_FRAMEWORK_BUNDLES = "org.apache.osgi.bundles"; |
| |
| /** |
| * The property to be set to ignore the system properties when building the |
| * Felix framework properties (value is "sling.ignoreSystemProperties"). If |
| * this is property is set to <code>true</code> (case does not matter), |
| * the system properties will not be used by |
| * {@link #loadConfigProperties(Map)}. |
| */ |
| public static final String SLING_IGNORE_SYSTEM_PROPERTIES = "sling.ignoreSystemProperties"; |
| |
| /** |
| * The name of the default launcher properties file to setup the environment |
| * for the <code>Felix</code> framework (value is "sling.properties"). |
| * <p> |
| * Extensions of this class may overwrite some or all properties in this |
| * file through Web Application parameters or other properties files. |
| */ |
| public static final String CONFIG_PROPERTIES = "sling.properties"; |
| |
| public static final String PROP_SYSTEM_PACKAGES = "org.apache.sling.launcher.system.packages"; |
| |
| public static final String PROP_EXTRA_CAPS = "org.apache.sling.launcher.system.capabilities.extra"; |
| |
| /** |
| * Timeout to wait for the initialized framework to actually stop for it to |
| * be reinitialized. This is set to a second, which should be ample time to |
| * do this. If this time passes without the framework being stopped, an |
| * error is issued. |
| */ |
| private static final long REINIT_TIMEOUT = 1000L; |
| |
| /** |
| * The simple logger to log messages during startup and shutdown to |
| */ |
| protected final Logger logger; |
| |
| private LaunchpadContentProvider resourceProvider; |
| |
| /** |
| * The <code>Felix</code> instance loaded on {@link #init()} and stopped |
| * on {@link #destroy()}. |
| */ |
| private Framework framework; |
| |
| /** |
| * Initializes this servlet by loading the framework configuration |
| * properties, starting the OSGi framework (Apache Felix) and exposing the |
| * system bundle context and the <code>Felix</code> instance as servlet |
| * context attributes. |
| * |
| * @throws BundleException if the framework cannot be initialized. |
| */ |
| public Sling(final Notifiable notifiable, |
| final Logger logger, |
| final LaunchpadContentProvider resourceProvider, |
| final Map<String, String> propOverwrite) |
| throws BundleException { |
| |
| this.logger = logger; |
| this.resourceProvider = resourceProvider; |
| |
| final long startedAt = System.currentTimeMillis(); |
| this.logger.log(Logger.LOG_INFO, "Starting Apache Sling"); |
| |
| // read the default parameters |
| final Map<String, String> props = this.loadConfigProperties(propOverwrite); |
| |
| // check for bootstrap command file |
| copyBootstrapCommandFile(props); |
| |
| // create the framework and start it |
| try { |
| |
| // initiate startup handler |
| final StartupManager startupManager = new StartupManager(props, logger); |
| |
| Framework tmpFramework = createFramework(notifiable, logger, props); |
| init(tmpFramework); |
| |
| final boolean restart = new BootstrapInstaller(tmpFramework.getBundleContext(), logger, |
| resourceProvider, startupManager.getMode()).install(); |
| startupManager.markInstalled(); |
| |
| if (restart) { |
| restart(tmpFramework); |
| tmpFramework = createFramework(notifiable, logger, props); |
| init(tmpFramework); |
| } |
| |
| new DefaultStartupHandler(tmpFramework.getBundleContext(), logger, startupManager, startedAt); |
| |
| // finally start |
| tmpFramework.start(); |
| |
| // only assign field if start succeeds |
| this.framework = tmpFramework; |
| } catch (final BundleException be) { |
| throw be; |
| } catch (final Exception e) { |
| // thrown by SlingFelix constructor |
| throw new BundleException("Uncaught Instantiation Issue: " + e, e); |
| } |
| |
| // log sucess message |
| this.logger.log(Logger.LOG_INFO, "Apache Sling started"); |
| } |
| |
| /** |
| * Destroys this servlet by shutting down the OSGi framework and hence the |
| * delegatee servlet if one is set at all. |
| */ |
| public final void destroy() { |
| if (framework != null) { |
| // get a private copy of the reference and remove the class ref |
| Framework myFramework; |
| synchronized (this) { |
| myFramework = framework; |
| framework = null; |
| } |
| |
| // shutdown the Felix container |
| if (myFramework != null) { |
| logger.log(Logger.LOG_INFO, "Shutting down Apache Sling"); |
| try { |
| |
| myFramework.stop(); |
| myFramework.waitForStop(0); |
| |
| } catch (BundleException be) { |
| |
| // may be thrown by stop, log but continue |
| logger.log(Logger.LOG_ERROR, |
| "Failure initiating Framework Shutdown", be); |
| |
| } catch (InterruptedException ie) { |
| |
| // may be thrown by waitForStop, log but continue |
| logger.log( |
| Logger.LOG_ERROR, |
| "Interrupted while waiting for the Framework Termination", |
| ie); |
| |
| } |
| |
| logger.log(Logger.LOG_INFO, "Apache Sling stopped"); |
| } |
| } |
| } |
| |
| // ---------- BundleActivator ---------------------------------------------- |
| |
| /** |
| * Called when the OSGi framework is being started. This implementation |
| * registers as a service listener for the |
| * <code>javax.servlet.Servlet</code> class and calls the |
| * {@link #doStartBundle()} method for implementations to execute more |
| * startup tasks. Additionally the <code>context</code> URL protocol |
| * handler is registered. |
| * |
| * @param bundleContext The <code>BundleContext</code> of the system |
| * bundle of the OSGi framework. |
| * @throws BundleException May be thrown if the {@link #doStartBundle()} throws. |
| */ |
| private final void startup(BundleContext bundleContext) { |
| |
| // register the context URL handler |
| Hashtable<String, Object> props = new Hashtable<String, Object>(); |
| props.put(URLConstants.URL_HANDLER_PROTOCOL, new String[] { "context" }); |
| ContextProtocolHandler contextHandler = new ContextProtocolHandler( |
| this.resourceProvider); |
| bundleContext.registerService(URLStreamHandlerService.class.getName(), |
| contextHandler, props); |
| |
| // register the platform MBeanServer |
| MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer(); |
| Hashtable<String, Object> mbeanProps = new Hashtable<String, Object>(); |
| try { |
| ObjectName beanName = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate"); |
| AttributeList attrs = platformMBeanServer.getAttributes(beanName, |
| new String[] { "MBeanServerId", "SpecificationName", |
| "SpecificationVersion", "SpecificationVendor", |
| "ImplementationName", "ImplementationVersion", |
| "ImplementationVendor" }); |
| for (Object object : attrs) { |
| Attribute attr = (Attribute) object; |
| if (attr.getValue() != null) { |
| mbeanProps.put(attr.getName(), attr.getValue().toString()); |
| } |
| } |
| } catch (Exception je) { |
| logger.log( |
| Logger.LOG_INFO, |
| "start: Cannot set service properties of Platform MBeanServer service, registering without", |
| je); |
| } |
| bundleContext.registerService(MBeanServer.class.getName(), |
| platformMBeanServer, mbeanProps); |
| bundleContext.registerService(LaunchpadContentProvider.class.getName(), resourceProvider, null); |
| } |
| |
| // ---------- Creating the framework instance |
| |
| @SuppressWarnings("unchecked") |
| private Framework createFramework(final Notifiable notifiable, |
| final Logger logger, @SuppressWarnings("rawtypes") Map props) |
| throws Exception { |
| props.put(FelixConstants.LOG_LOGGER_PROP, logger); |
| return new SlingFelix(notifiable, props); |
| } |
| |
| private void init(final Framework framework) throws BundleException { |
| // initialize the framework |
| framework.init(); |
| |
| // do first startup setup |
| this.startup(framework.getBundleContext()); |
| } |
| |
| private void restart(final Framework framework) throws BundleException { |
| if ((framework.getState() & (Bundle.STARTING|Bundle.ACTIVE|Bundle.STOPPING)) != 0) { |
| if ( framework instanceof SlingFelix ) { |
| ((SlingFelix)framework).restart(); |
| } else { |
| framework.stop(); |
| } |
| try { |
| framework.waitForStop(REINIT_TIMEOUT); |
| } catch (InterruptedException ie) { |
| throw new BundleException( |
| "Interrupted while waiting for the framework stop before reinitialization"); |
| } |
| } |
| } |
| |
| // ---------- Configuration Loading |
| |
| /** |
| * Loads the configuration properties in the configuration property file |
| * associated with the framework installation; these properties are |
| * accessible to the framework and to bundles and are intended for |
| * configuration purposes. By default, the configuration property file is |
| * located in the <tt>conf/</tt> directory of the Felix installation |
| * directory and is called "<tt>config.properties</tt>". The |
| * installation directory of Felix is assumed to be the parent directory of |
| * the <tt>framework.jar</tt> file as found on the system class path property. |
| * The precise file from which to load configuration properties can be set |
| * by initializing the "<tt>framework.config.properties</tt>" system |
| * property to an arbitrary URL. |
| * |
| * @return A <tt>Properties</tt> instance or <tt>null</tt> if there was |
| * an error. |
| */ |
| private Map<String, String> loadConfigProperties( |
| final Map<String, String> propOverwrite) throws BundleException { |
| // The config properties file is either specified by a system |
| // property or it is in the same directory as the Felix JAR file. |
| // Try to load it from one of these places. |
| final Map<String, String> staticProps = new HashMap<String, String>(); |
| |
| // Read the embedded (default) properties file. |
| this.load(staticProps, CONFIG_PROPERTIES); |
| |
| // resolve inclusions (and remove property) |
| this.loadIncludes(staticProps, null); |
| |
| // overwrite default properties with initial overwrites |
| if (propOverwrite != null) { |
| staticProps.putAll(propOverwrite); |
| } |
| |
| // check whether sling.home is overwritten by system property |
| String slingHome = staticProps.get(SharedConstants.SLING_HOME); |
| if (slingHome == null || slingHome.length() == 0) { |
| throw new BundleException("sling.home property is missing, cannot start"); |
| } |
| |
| // resolve variables and ensure sling.home is an absolute path |
| slingHome = Util.substVars(slingHome, SharedConstants.SLING_HOME, null, staticProps); |
| File slingHomeFile = new File(slingHome).getAbsoluteFile(); |
| slingHome = slingHomeFile.getAbsolutePath(); |
| |
| // overlay with ${sling.home}/sling.properties |
| this.logger.log(Logger.LOG_INFO, "Starting Apache Sling in " + slingHome); |
| File propFile = getSlingProperties(slingHome, staticProps); |
| this.load(staticProps, propFile); |
| |
| // migrate old properties to new properties |
| migrateProp(staticProps, "framework.cache.profiledir", Constants.FRAMEWORK_STORAGE); |
| migrateProp(staticProps, "sling.osgi-core-packages", "osgi-core-packages"); |
| migrateProp(staticProps, "sling.osgi-compendium-services", "osgi-compendium-services"); |
| |
| // migrate initial start level property: Felix used to have |
| // framework.startlevel.framework, later moved to org.osgi.framework.startlevel |
| // and finally now uses org.osgi.framework.startlevel.beginning as |
| // speced in the latest R 4.2 draft (2009/03/10). We first check the |
| // intermediate Felix property, then the initial property, thus allowing |
| // the older (and more probable value) to win |
| migrateProp(staticProps, "org.osgi.framework.startlevel", Constants.FRAMEWORK_BEGINNING_STARTLEVEL); |
| migrateProp(staticProps, "framework.startlevel.framework", Constants.FRAMEWORK_BEGINNING_STARTLEVEL); |
| |
| // create a copy of the properties to perform variable substitution |
| final Map<String, String> runtimeProps = new HashMap<String, String>(); |
| runtimeProps.putAll(staticProps); |
| |
| // check system properties for any overrides (except sling.home !) |
| String ignoreSystemProperties = runtimeProps.get(SLING_IGNORE_SYSTEM_PROPERTIES); |
| if (!"true".equalsIgnoreCase(ignoreSystemProperties)) { |
| for (String name : runtimeProps.keySet()) { |
| String sysProp = System.getProperty(name); |
| if (sysProp != null) { |
| runtimeProps.put(name, sysProp); |
| } |
| } |
| } |
| |
| // resolve inclusions again |
| this.loadIncludes(runtimeProps, slingHome); |
| |
| // overwrite properties, this is not persisted as such |
| this.loadPropertiesOverride(runtimeProps); |
| |
| // resolve boot delegation and system packages |
| this.resolve(runtimeProps, "org.osgi.framework.bootdelegation", |
| "sling.bootdelegation."); |
| this.resolve(runtimeProps, "org.osgi.framework.system.packages", |
| "sling.system.packages."); |
| |
| // reset back the sling home property |
| // might have been overwritten by system properties, included |
| // files or the sling.properties file |
| staticProps.put(SharedConstants.SLING_HOME, slingHome); |
| runtimeProps.put(SharedConstants.SLING_HOME, slingHome); |
| runtimeProps.put(SLING_HOME_URL, slingHomeFile.toURI().toString()); |
| |
| // add property file locations |
| runtimeProps.put(SharedConstants.SLING_PROPERTIES, propFile.getAbsolutePath()); |
| runtimeProps.put(SharedConstants.SLING_PROPERTIES_URL, propFile.toURI().toString()); |
| |
| // Perform variable substitution for system properties. |
| for (Entry<String, String> entry : runtimeProps.entrySet()) { |
| entry.setValue(Util.substVars(entry.getValue(), entry.getKey(), null, |
| runtimeProps)); |
| } |
| |
| // look for context:/ URLs to substitute |
| for (Entry<String, String> entry : runtimeProps.entrySet()) { |
| String name = entry.getKey(); |
| String value = entry.getValue(); |
| if (value != null && value.startsWith("context:/")) { |
| String path = value.substring("context:/".length() - 1); |
| |
| InputStream src = this.resourceProvider.getResourceAsStream(path); |
| if (src != null) { |
| File target = new File(slingHome, path); |
| OutputStream dest = null; |
| try { |
| // only copy file if not existing |
| if (!target.exists()) { |
| target.getParentFile().mkdirs(); |
| dest = new FileOutputStream(target); |
| byte[] buf = new byte[2048]; |
| int rd; |
| while ((rd = src.read(buf)) >= 0) { |
| dest.write(buf, 0, rd); |
| } |
| } |
| |
| // after copying replace property and add url property |
| entry.setValue(target.getAbsolutePath()); |
| |
| // also set the new property on the unsubstituted props |
| staticProps.put(name, "${sling.home}" + path); |
| |
| } catch (IOException ioe) { |
| this.logger.log(Logger.LOG_ERROR, "Cannot copy file " |
| + value + " to " + target, ioe); |
| } finally { |
| if (dest != null) { |
| try { |
| dest.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| try { |
| src.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| } |
| } |
| } |
| |
| // write the unsubstituted properties back to the overlay file |
| OutputStream os = null; |
| try { |
| // ensure parent folder(s) |
| propFile.getParentFile().mkdirs(); |
| |
| os = new FileOutputStream(propFile); |
| |
| // copy the values into a temporary properties structure to store |
| Properties tmp = new Properties(); |
| tmp.putAll(staticProps); |
| |
| // remove properties where overlay makes no sense |
| tmp.remove(SharedConstants.SLING_HOME); |
| tmp.remove(SharedConstants.SLING_LAUNCHPAD); |
| tmp.remove(SharedConstants.SLING_PROPERTIES); |
| |
| tmp.store(os, "Overlay properties for configuration"); |
| } catch (Exception ex) { |
| this.logger.log(Logger.LOG_ERROR, |
| "Error loading overlay properties from " + propFile, ex); |
| } finally { |
| if (os != null) { |
| try { |
| os.close(); |
| } catch (IOException ex2) { |
| // Nothing we can do. |
| } |
| } |
| } |
| |
| |
| Map<String, String> result = new HashMap<>(); |
| |
| for (Entry<String, String> entry : runtimeProps.entrySet()) { |
| result.put(entry.getKey(), entry.getValue().replace("{dollar}", "$")); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Scans the properties for any properties starting with the given |
| * <code>prefix</code> (e.g. <code>sling.bootdelegation.</code>). |
| * <ol> |
| * <li>Each such property is checked, whether it actually starts with |
| * <code>prefix<b>class.</b></code>. If so, the rest of the property |
| * name is assumed to be a fully qualified class name which is check, |
| * whether it is visible. If so, the value of the property is appended to |
| * the value of the <code>osgiProp</code>. If the class cannot be loaded, |
| * the property is ignored. |
| * <li>Otherwise, if the property does not contain a fully qualified class |
| * name, the value of the property is simply appended to the |
| * <code>osgiProp</code>. |
| * </ol> |
| * |
| * @param props The <code>Properties</code> to be scanned. |
| * @param osgiProp The name of the property in <code>props</code> to which |
| * any matching property values are appended. |
| * @param prefix The prefix of properties to handle. |
| */ |
| private void resolve(Map<String, String> props, String osgiProp, |
| String prefix) { |
| final String propVal = props.get(osgiProp); |
| StringBuffer prop = new StringBuffer(propVal == null ? "" : propVal); |
| boolean mod = false; |
| for (Entry<String, String> pEntry : props.entrySet()) { |
| String key = pEntry.getKey(); |
| if (key.startsWith(prefix)) { |
| if (key.indexOf("class.") == prefix.length()) { |
| // prefix is followed by checker class name |
| String className = key.substring(prefix.length() |
| + "class.".length()); |
| try { |
| this.getClass().getClassLoader().loadClass(className); |
| } catch (Throwable t) { |
| // don't really care, but class checking failed, so we |
| // do not add |
| this.logger.log(Logger.LOG_DEBUG, "Class " + className |
| + " not found. Ignoring '" + pEntry.getValue() |
| + "' for property " + osgiProp); |
| continue; |
| } |
| } |
| |
| // get here if class is known or no checker class |
| this.logger.log(Logger.LOG_DEBUG, "Adding '" |
| + pEntry.getValue() + "' to property " + osgiProp); |
| if (prop.length() > 0) { |
| prop.append(','); |
| } |
| prop.append(pEntry.getValue()); |
| mod = true; |
| } |
| } |
| |
| // replace the property with the modified property |
| if (mod) { |
| this.logger.log(Logger.LOG_DEBUG, "Setting property " + osgiProp |
| + " to " + prop.toString()); |
| props.put(osgiProp, prop.toString()); |
| } |
| } |
| |
| /** |
| * Converts an old Felix framework property into a new (standard or modified |
| * Felix framework) property. If a property named <code>oldName</code> does |
| * not exist in the <code>props</code> map, the map is not modified. If such |
| * a property exists it is removed and add to the map with the |
| * <code>newName</code> key. If both properties <code>oldName</code> and |
| * <code>newName</code> exist, the property <code>newName</code> is replaced |
| * with the value of the property <code>oldName</code>. |
| * |
| * @param props The map of properties containing the property to rename |
| * @param oldName The old key of the property value |
| * @param newName The new key of the property value |
| */ |
| private void migrateProp(Map<String, String> props, String oldName, |
| String newName) { |
| String propValue = props.remove(oldName); |
| if (propValue != null) { |
| String previousNewValue = props.put(newName, propValue); |
| if (previousNewValue != null) { |
| logger.log(Logger.LOG_WARNING, "Old value (" + previousNewValue |
| + ") of property " + newName + " by value: " + propValue); |
| } else { |
| logger.log(Logger.LOG_INFO, "Property " + oldName + " (" |
| + propValue + ") renamed to " + newName); |
| } |
| } else { |
| logger.log(Logger.LOG_DEBUG, "Property " + oldName |
| + " does not exist, nothing to do"); |
| } |
| } |
| |
| // ---------- Extension support -------------------------------------------- |
| |
| /** |
| * Loads additional properties into the <code>properties</code> object. |
| * <p> |
| * This implementation does nothing and may be overwritten by extensions |
| * requiring additional properties to be set. |
| * <p> |
| * This method is called when the servlet is initialized to prepare the |
| * configuration for <code>Felix</code>. Implementations may add |
| * properties from implementation specific sources. Properties added here |
| * overwrite properties loaded from the default properties file and may be |
| * overwritten by parameters set in the web application. |
| * <p> |
| * The <code>properties</code> object has not undergone variable |
| * substition and properties added by this method may also contain values |
| * refererring to other properties. |
| * <p> |
| * The properties added in this method will not be persisted in the |
| * <code>sling.properties</code> file in the <code>sling.home</code> |
| * directory. |
| * |
| * @param properties The <code>Properties</code> object to which custom |
| * properties may be added. |
| */ |
| protected void loadPropertiesOverride(@SuppressWarnings("unused") Map<String, String> properties) { |
| } |
| |
| /** |
| * Returns the <code>BundleContext</code> of the system bundle of the OSGi |
| * framework launched by this servlet. This method only returns a non-<code>null</code> |
| * object after the system bundle of the framework has been started and |
| * before it is being stopped. |
| */ |
| public final BundleContext getBundleContext() { |
| return this.framework.getBundleContext(); |
| } |
| |
| // ---------- Property file support ---------------------------------------- |
| |
| /** |
| * Returns the abstract path name to the <code>sling.properties</code> file. |
| */ |
| private File getSlingProperties(final String slingHome, |
| final Map<String, String> properties) { |
| final String prop = properties.get(SharedConstants.SLING_PROPERTIES); |
| if (prop == null) { |
| return new File(slingHome, CONFIG_PROPERTIES); |
| } |
| |
| final File propFile = new File(prop); |
| return propFile.isAbsolute() ? propFile : new File(slingHome, prop); |
| } |
| |
| /** |
| * Looks for <code>sling.include</code> and <code>sling.include.*</code> |
| * properties in the <code>props</code> and loads properties form the |
| * respective locations. |
| * <p> |
| * Each <code>sling.include</code> (or <code>sling.include.*</code>) |
| * property may contain a comma-separated list of resource and/or file names |
| * to be loaded from. The includes are loaded in alphabetical order of the |
| * property names. |
| * <p> |
| * Each reasource path is first tried to be loaded through the |
| * {@link #resourceProvider}. If that fails, the resource path is tested as |
| * a file. If relative <code>slingHome</code> is used as the parent if not |
| * <code>null</code>, otherwise the current working directory is used as |
| * the parent. |
| * <p> |
| * Any non-existing resource is silently ignored. |
| * <p> |
| * When the method returns, the <code>sling.include</code> and |
| * <code>sling.include.*</code> properties are not contained in the |
| * <code>props</code> any more. |
| * |
| * @param props The <code>Properties</code> containing the |
| * <code>sling.include</code> and <code>sling.include.*</code> |
| * properties. This is also the destination for the new |
| * properties loaded. |
| * @param slingHome The parent directory used to resolve relative path names |
| * if loading from a file. This may be <code>null</code> in |
| * which case the current working directory is used as the |
| * parent. |
| */ |
| private void loadIncludes(Map<String, String> props, String slingHome) { |
| // Build the sort map of include properties first |
| // and remove include elements from the properties |
| SortedMap<String, String> includes = new TreeMap<String, String>(); |
| for (Iterator<Entry<String, String>> pi = props.entrySet().iterator(); pi.hasNext();) { |
| Entry<String, String> entry = pi.next(); |
| if (entry.getKey().startsWith("sling.include.") |
| || entry.getKey().equals("sling.include")) { |
| includes.put(entry.getKey(), entry.getValue()); |
| pi.remove(); |
| } |
| } |
| |
| for (Iterator<Entry<String, String>> ii = includes.entrySet().iterator(); ii.hasNext();) { |
| Map.Entry<String, String> entry = ii.next(); |
| String key = entry.getKey(); |
| String include = entry.getValue(); |
| |
| // ensure variable resolution on this property |
| include = Util.substVars(include, key, null, props); |
| |
| StringTokenizer tokener = new StringTokenizer(include, ","); |
| while (tokener.hasMoreTokens()) { |
| String file = tokener.nextToken().trim(); |
| InputStream is = this.resourceProvider.getResourceAsStream(file); |
| try { |
| if (is == null && slingHome != null) { |
| File resFile = new File(file); |
| if (!resFile.isAbsolute()) { |
| resFile = new File(slingHome, file); |
| } |
| if (resFile.canRead()) { |
| is = new FileInputStream(resFile); |
| file = resFile.getAbsolutePath(); // for logging |
| } |
| } |
| |
| if (is != null) { |
| this.load(props, is); |
| } |
| } catch (IOException ioe) { |
| this.logger.log(Logger.LOG_ERROR, |
| "Error loading config properties from " + file, ioe); |
| } finally { |
| if ( is != null ) { |
| try { |
| is.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Load properties from the given resource file, which is accessed through |
| * the {@link #resourceProvider}. If the resource does not exist, nothing |
| * is loaded. |
| * |
| * @param props The <code>Properties</code> into which the loaded |
| * properties are loaded |
| * @param resource The resource from which to load the resources |
| */ |
| private void load(Map<String, String> props, String resource) { |
| InputStream is = this.resourceProvider.getResourceAsStream(resource); |
| if (is != null) { |
| try { |
| this.load(props, is); |
| } catch (IOException ioe) { |
| this.logger.log(Logger.LOG_ERROR, |
| "Error loading config properties from " + resource, ioe); |
| } finally { |
| try { |
| is.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Load properties from the given file. If the resource cannot be read from |
| * (e.g. because it does not exist), nothing is loaded. |
| * |
| * @param props The <code>Properties</code> into which the loaded |
| * properties are loaded |
| * @param file The <code>File</code> to load the properties from |
| */ |
| private void load(Map<String, String> props, File file) { |
| if (file != null && file.canRead()) { |
| try { |
| this.load(props, new FileInputStream(file)); |
| } catch (IOException ioe) { |
| this.logger.log(Logger.LOG_ERROR, |
| "Error loading config properties from " |
| + file.getAbsolutePath(), ioe); |
| } |
| } |
| } |
| |
| private void load(Map<String, String> props, InputStream ins) |
| throws IOException { |
| try { |
| Properties tmp = new Properties(); |
| tmp.load(ins); |
| |
| for (Map.Entry<Object, Object> entry : tmp.entrySet()) { |
| final String value = (String)entry.getValue(); |
| props.put((String) entry.getKey(), (value == null ? null : value.trim())); |
| } |
| } finally { |
| try { |
| ins.close(); |
| } catch (IOException ioe2) { |
| // ignore |
| } |
| } |
| } |
| |
| private void copyBootstrapCommandFile(final Map<String, String> props) { |
| // check last modification date |
| final URL url = this.resourceProvider.getResource(BootstrapInstaller.BOOTSTRAP_CMD_FILENAME); |
| if ( url != null ) { |
| this.logger.log(Logger.LOG_DEBUG, "Checking last modification date of bootstrap command file."); |
| InputStream is = null; |
| OutputStream os = null; |
| try { |
| final long lastModified = url.openConnection().getLastModified(); |
| final File launchpadHome = new File(props.get(SharedConstants.SLING_LAUNCHPAD)); |
| final File cmdFile = new File(launchpadHome, BootstrapInstaller.BOOTSTRAP_CMD_FILENAME); |
| boolean copyFile = true; |
| if ( cmdFile.exists() && cmdFile.lastModified() >= lastModified ) { |
| copyFile = false; |
| } |
| if ( copyFile ) { |
| this.logger.log(Logger.LOG_INFO, "Copying bootstrap command file."); |
| is = this.resourceProvider.getResourceAsStream(BootstrapInstaller.BOOTSTRAP_CMD_FILENAME); |
| os = new FileOutputStream(cmdFile); |
| final byte[] buffer = new byte[2048]; |
| int l; |
| while ( (l = is.read(buffer, 0, buffer.length)) != -1 ) { |
| os.write(buffer, 0, l); |
| } |
| } |
| |
| } catch (final IOException ioe) { |
| this.logger.log(Logger.LOG_INFO, "Ignoring exception during processing of bootstrap command file.", ioe); |
| } finally { |
| if ( is != null ) { |
| try { is.close(); } catch (final IOException ignore) {} |
| } |
| if ( os != null ) { |
| try { os.close(); } catch (final IOException ignore) {} |
| } |
| } |
| } else { |
| this.logger.log(Logger.LOG_DEBUG, "Bootstrap command file not found."); |
| } |
| |
| } |
| } |