blob: 15914ab53a2c9409b9c66a19a1055100f5668d9f [file] [log] [blame]
/*
* 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>${&lt;prop-name&gt;}</code>
* where <code>&lt;prop-name&gt;</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.
}
}
}
return runtimeProps;
}
/**
* 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.");
}
}
}