Save This Page
Home » org.apache.sling.launchpad.base-2.2.0-source-release » org.apache.sling.launchpad.base.impl » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.sling.launchpad.base.impl;
   18   
   19   import java.io.File;
   20   import java.io.FileInputStream;
   21   import java.io.FileOutputStream;
   22   import java.io.IOException;
   23   import java.io.InputStream;
   24   import java.io.OutputStream;
   25   import java.net.MalformedURLException;
   26   import java.util.ArrayList;
   27   import java.util.HashMap;
   28   import java.util.Hashtable;
   29   import java.util.Iterator;
   30   import java.util.List;
   31   import java.util.Map;
   32   import java.util.Properties;
   33   import java.util.Set;
   34   import java.util.SortedMap;
   35   import java.util.StringTokenizer;
   36   import java.util.TreeMap;
   37   import java.util.TreeSet;
   38   import java.util.Map.Entry;
   39   
   40   import org.apache.felix.framework.Felix;
   41   import org.apache.felix.framework.Logger;
   42   import org.apache.felix.framework.util.FelixConstants;
   43   import org.apache.sling.launchpad.base.shared.Notifiable;
   44   import org.apache.sling.launchpad.base.shared.SharedConstants;
   45   import org.osgi.framework.BundleActivator;
   46   import org.osgi.framework.BundleContext;
   47   import org.osgi.framework.BundleException;
   48   import org.osgi.framework.Constants;
   49   import org.osgi.framework.Version;
   50   import org.osgi.service.url.URLConstants;
   51   import org.osgi.service.url.URLStreamHandlerService;
   52   
   53   /**
   54    * The <code>Sling</code> serves as the starting point for Sling.
   55    * <ul>
   56    * <li>The {@link #Sling(Notifiable, Logger, ResourceProvider, Map)} method launches Apache
   57    * <code>Felix</code> as the OSGi framework implementation we use.
   58    * </ul>
   59    * <p>
   60    * <b>Launch Configuration</b>
   61    * <p>
   62    * The Apache <code>Felix</code> framework requires configuration parameters to
   63    * be specified for startup. This servlet builds the list of parameters from
   64    * three locations:
   65    * <ol>
   66    * <li>The <code>sling.properties</code> file is read from the servlet class
   67    * path. This properties file contains default settings.</li>
   68    * <li>Extensions of this servlet may provide additional properties to be loaded
   69    * overwriting the {@link #loadPropertiesOverride(Map)} method.
   70    * <li>Finally, web application init parameters are added to the properties and
   71    * may overwrite existing properties of the same name(s).
   72    * </ol>
   73    * <p>
   74    * After loading all properties, variable substitution takes place on the
   75    * property values. A variable is indicated as <code>${&lt;prop-name&gt;}</code>
   76    * where <code>&lt;prop-name&gt;</code> is the name of a system or configuration
   77    * property (configuration properties override system properties). Variables may
   78    * be nested and are resolved from inner-most to outer-most. For example, the
   79    * property value <code>${outer-${inner}}</code> is resolved by first resolving
   80    * <code>${inner}</code> and then resolving the property whose name is the
   81    * catenation of <code>outer-</code> and the result of resolving
   82    * <code>${inner}</code>.
   83    * <p>
   84    */
   85   public class Sling implements BundleActivator {
   86   
   87       /** Pseduo class version ID to keep the IDE quite. */
   88       private static final long serialVersionUID = 1L;
   89   
   90       /**
   91        * The name of the configuration property defining the Sling home directory
   92        * as an URL (value is "sling.home.url").
   93        * <p>
   94        * The value of this property is assigned the value of
   95        * <code>new File(${sling.home}).toURI().toString()</code> before
   96        * resolving the property variables.
   97        *
   98        * @see SharedConstants#SLING_HOME
   99        */
  100       public static final String SLING_HOME_URL = "sling.home.url";
  101   
  102       /**
  103        * The name of the configuration property defining the JCR home directory
  104        * (value is "sling.repository.home").
  105        * <p>
  106        * The value of this property could be set as a system property, init-param in
  107        * web.xml or property in sling.properties.
  108        * <p>
  109        * Default value to #SLING_HOME/repository_name
  110        */
  111       public static final String JCR_REPO_HOME = "sling.repository.home";
  112   
  113       /**
  114        * The name of the configuration property defining the URL of an existing
  115        * repository config file (repository.xml).
  116        * <p>
  117        * The value of this property could be set as a system property, init-param in
  118        * web.xml or property in sling.properties.
  119        * <p>
  120        * Default value to #SLING_HOME/repository_name/repository.xml
  121        */
  122       public static final String JCR_REPO_CONFIG_FILE_URL = "sling.repository.config.file.url";
  123   
  124       /**
  125        * The name of the configuration property defining a properties file
  126        * defining a list of bundles, which are installed into the framework when
  127        * it has been launched (value is "org.apache.osgi.bundles").
  128        * <p>
  129        * This configuration property is generally set in the web application
  130        * configuration and may be referenced in all property files (default, user
  131        * supplied and web application parameters) used to build the framework
  132        * configuration.
  133        */
  134       public static final String OSGI_FRAMEWORK_BUNDLES = "org.apache.osgi.bundles";
  135   
  136       /**
  137        * The property to be set to ignore the system properties when building the
  138        * Felix framework properties (value is "sling.ignoreSystemProperties"). If
  139        * this is property is set to <code>true</code> (case does not matter),
  140        * the system properties will not be used by
  141        * {@link #loadConfigProperties(Map)}.
  142        */
  143       public static final String SLING_IGNORE_SYSTEM_PROPERTIES = "sling.ignoreSystemProperties";
  144   
  145       /**
  146        * The name of the default launcher properties file to setup the environment
  147        * for the <code>Felix</code> framework (value is "sling.properties").
  148        * <p>
  149        * Extensions of this class may overwrite some or all properties in this
  150        * file through Web Application parameters or other properties files.
  151        */
  152       public static final String CONFIG_PROPERTIES = "sling.properties";
  153   
  154       public static final String PROP_SYSTEM_PACKAGES = "org.apache.sling.launcher.system.packages";
  155   
  156       /**
  157        * List of multiple Execution Environment names supported by various
  158        * Java Runtime versions.
  159        * @see #setExecutionEnvironment(Map)
  160        */
  161       private static final String[][] EE_NAMES = { { "JRE-1.1" }, { "J2SE-1.2" },
  162           { "J2SE-1.3", "OSGi/Minimum-1.0", "CDC-1.0/Foundation-1.0" },
  163           { "J2SE-1.4", "OSGi/Minimum-1.1", "CDC-1.1/Foundation-1.1" },
  164           { "J2SE-1.5" }, { "JavaSE-1.6" } };
  165   
  166       /**
  167        * The simple logger to log messages during startup and shutdown to
  168        */
  169       protected final Logger logger;
  170   
  171       private ResourceProvider resourceProvider;
  172   
  173       /**
  174        * The <code>Felix</code> instance loaded on {@link #init()} and stopped
  175        * on {@link #destroy()}.
  176        */
  177       private Felix felix;
  178   
  179       /**
  180        * The <code>BundleContext</code> of the OSGi framework system bundle.
  181        * This is used for service registration and service access to get at the
  182        * delegatee servlet.
  183        */
  184       private BundleContext bundleContext;
  185   
  186       /**
  187        * Initializes this servlet by loading the framework configuration
  188        * properties, starting the OSGi framework (Apache Felix) and exposing the
  189        * system bundle context and the <code>Felix</code> instance as servlet
  190        * context attributes.
  191        *
  192        * @throws BundleException if the framework cannot be initialized.
  193        */
  194       public Sling(Notifiable notifiable, Logger logger,
  195               ResourceProvider resourceProvider, Map<String, String> propOverwrite)
  196               throws BundleException {
  197   
  198           this.logger = logger;
  199           this.resourceProvider = resourceProvider;
  200   
  201           this.logger.log(Logger.LOG_INFO, "Starting Sling");
  202   
  203           // read the default parameters
  204           Map<String, String> props = this.loadConfigProperties(propOverwrite);
  205   
  206           // check for auto-start bundles
  207           this.setInstallBundles(props);
  208   
  209           // ensure execution environment
  210           this.setExecutionEnvironment(props);
  211   
  212           // prepare bootstrap installer and ensure the framework only goes into
  213           // level 1 in the first place
  214           final BootstrapInstaller bi = new BootstrapInstaller(logger,
  215               resourceProvider, props);
  216           props.put(Constants.FRAMEWORK_BEGINNING_STARTLEVEL, "1");
  217   
  218           // the custom activator list just contains this servlet
  219           List<BundleActivator> activators = new ArrayList<BundleActivator>();
  220           activators.add(this);
  221           activators.add(bi);
  222   
  223           // create the framework and start it
  224           Map<String, Object> felixProps = new HashMap<String, Object>(props);
  225           felixProps.put(FelixConstants.LOG_LOGGER_PROP, logger);
  226           felixProps.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, activators);
  227           try {
  228               Felix tmpFelix = new SlingFelix(notifiable, felixProps);
  229               tmpFelix.init(); // call needed due to FELIX-910
  230               tmpFelix.start();
  231   
  232               // only assign field if start succeeds
  233               this.felix = tmpFelix;
  234           } catch (BundleException be) {
  235               throw be;
  236           } catch (Exception e) {
  237               // thrown by SlingFelix constructor
  238               throw new BundleException("Uncaught Instantiation Issue: " + e, e);
  239           }
  240   
  241           // log sucess message
  242           this.logger.log(Logger.LOG_INFO, "Sling started");
  243       }
  244   
  245       /**
  246        * Destroys this servlet by shutting down the OSGi framework and hence the
  247        * delegatee servlet if one is set at all.
  248        */
  249       public final void destroy() {
  250           if (felix != null) {
  251               // get a private copy of the reference and remove the class ref
  252               Felix myFelix;
  253               synchronized (this) {
  254                   myFelix = felix;
  255                   felix = null;
  256               }
  257   
  258               // shutdown the Felix container
  259               if (myFelix != null) {
  260                   logger.log(Logger.LOG_INFO, "Shutting down Sling");
  261                   try {
  262   
  263                       myFelix.stop();
  264                       myFelix.waitForStop(0);
  265   
  266                   } catch (BundleException be) {
  267   
  268                       // may be thrown by stop, log but continue
  269                       logger.log(Logger.LOG_ERROR,
  270                           "Failure initiating Framework Shutdown", be);
  271   
  272                   } catch (InterruptedException ie) {
  273   
  274                       // may be thrown by waitForStop, log but continue
  275                       logger.log(
  276                           Logger.LOG_ERROR,
  277                           "Interrupted while waiting for the Framework Termination",
  278                           ie);
  279   
  280                   }
  281   
  282                   logger.log(Logger.LOG_INFO, "Sling stopped");
  283               }
  284           }
  285       }
  286   
  287       // ---------- BundleActivator ----------------------------------------------
  288   
  289       /**
  290        * Called when the OSGi framework is being started. This implementation
  291        * registers as a service listener for the
  292        * <code>javax.servlet.Servlet</code> class and calls the
  293        * {@link #doStartBundle()} method for implementations to execute more
  294        * startup tasks. Additionally the <code>context</code> URL protocol
  295        * handler is registered.
  296        *
  297        * @param bundleContext The <code>BundleContext</code> of the system
  298        *            bundle of the OSGi framework.
  299        * @throws Exception May be thrown if the {@link #doStartBundle()} throws.
  300        */
  301       public final void start(BundleContext bundleContext) throws Exception {
  302           this.bundleContext = bundleContext;
  303   
  304           // register the context URL handler
  305           Hashtable<String, Object> props = new Hashtable<String, Object>();
  306           props.put(URLConstants.URL_HANDLER_PROTOCOL, new String[] { "context" });
  307           ContextProtocolHandler contextHandler = new ContextProtocolHandler(
  308               this.resourceProvider);
  309           bundleContext.registerService(URLStreamHandlerService.class.getName(),
  310               contextHandler, props);
  311   
  312           // execute optional bundle startup tasks of an extension
  313           this.doStartBundle();
  314       }
  315   
  316       /**
  317        * Called when the OSGi framework is being shut down. This implementation
  318        * first calls the {@link #doStopBundle()} method method before
  319        * unregistering as a service listener and ungetting an servlet delegatee if
  320        * one has been acquired.
  321        *
  322        * @param bundleContext The <code>BundleContext</code> of the system
  323        *            bundle of the OSGi framework.
  324        */
  325       public final void stop(BundleContext bundleContext) {
  326           // execute optional bundle stop tasks of an extension
  327           try {
  328               this.doStopBundle();
  329           } catch (Exception e) {
  330               this.logger.log(Logger.LOG_ERROR, "Unexpected exception caught", e);
  331           }
  332   
  333           // drop bundle context reference
  334           this.bundleContext = null;
  335       }
  336   
  337       // ---------- Configuration Loading ----------------------------------------
  338   
  339       /**
  340        * Loads the configuration properties in the configuration property file
  341        * associated with the framework installation; these properties are
  342        * accessible to the framework and to bundles and are intended for
  343        * configuration purposes. By default, the configuration property file is
  344        * located in the <tt>conf/</tt> directory of the Felix installation
  345        * directory and is called "<tt>config.properties</tt>". The
  346        * installation directory of Felix is assumed to be the parent directory of
  347        * the <tt>felix.jar</tt> file as found on the system class path property.
  348        * The precise file from which to load configuration properties can be set
  349        * by initializing the "<tt>felix.config.properties</tt>" system
  350        * property to an arbitrary URL.
  351        *
  352        * @return A <tt>Properties</tt> instance or <tt>null</tt> if there was
  353        *         an error.
  354        */
  355       private Map<String, String> loadConfigProperties(
  356               Map<String, String> propOverwrite) throws BundleException {
  357           // The config properties file is either specified by a system
  358           // property or it is in the same directory as the Felix JAR file.
  359           // Try to load it from one of these places.
  360           final Map<String, String> staticProps = new HashMap<String, String>();
  361   
  362           // Read the properties file.
  363           this.load(staticProps, CONFIG_PROPERTIES);
  364   
  365           // resolve inclusions (and remove property)
  366           this.loadIncludes(staticProps, null);
  367   
  368           // overwrite default properties with initial overwrites
  369           if (propOverwrite != null) {
  370               staticProps.putAll(propOverwrite);
  371           }
  372   
  373           // check whether sling.home is overwritten by system property
  374           String slingHome = staticProps.get(SharedConstants.SLING_HOME);
  375           if (slingHome == null || slingHome.length() == 0) {
  376               throw new BundleException("sling.home property is missing, cannot start");
  377           }
  378   
  379           // resolve variables and ensure sling.home is an absolute path
  380           slingHome = substVars(slingHome, SharedConstants.SLING_HOME, null, staticProps);
  381           File slingHomeFile = new File(slingHome).getAbsoluteFile();
  382           slingHome = slingHomeFile.getAbsolutePath();
  383   
  384           // overlay with ${sling.home}/sling.properties
  385           this.logger.log(Logger.LOG_INFO, "Starting sling in " + slingHome);
  386           File propFile = new File(slingHome, CONFIG_PROPERTIES);
  387           this.load(staticProps, propFile);
  388   
  389           // migrate old properties to new properties
  390           migrateProp(staticProps, "felix.cache.profiledir", Constants.FRAMEWORK_STORAGE);
  391           migrateProp(staticProps, "sling.osgi-core-packages", "osgi-core-packages");
  392           migrateProp(staticProps, "sling.osgi-compendium-services", "osgi-compendium-services");
  393   
  394           // migrate initial start level property: Felix used to have
  395           // felix.startlevel.framework, later moved to org.osgi.framework.startlevel
  396           // and finally now uses org.osgi.framework.startlevel.beginning as
  397           // speced in the latest R 4.2 draft (2009/03/10). We first check the
  398           // intermediate Felix property, then the initial property, thus allowing
  399           // the older (and more probable value) to win
  400           migrateProp(staticProps, "org.osgi.framework.startlevel", Constants.FRAMEWORK_BEGINNING_STARTLEVEL);
  401           migrateProp(staticProps, "felix.startlevel.framework", Constants.FRAMEWORK_BEGINNING_STARTLEVEL);
  402   
  403           // create a copy of the properties to perform variable substitution
  404           final Map<String, String> runtimeProps = new HashMap<String, String>();
  405           runtimeProps.putAll(staticProps);
  406   
  407           // check system properties for any overrides (except sling.home !)
  408           String ignoreSystemProperties = runtimeProps.get(SLING_IGNORE_SYSTEM_PROPERTIES);
  409           if (!"true".equalsIgnoreCase(ignoreSystemProperties)) {
  410               for (String name : runtimeProps.keySet()) {
  411                   String sysProp = System.getProperty(name);
  412                   if (sysProp != null) {
  413                       runtimeProps.put(name, sysProp);
  414                   }
  415               }
  416           }
  417   
  418           // resolve inclusions again
  419           this.loadIncludes(runtimeProps, slingHome);
  420   
  421           // overwrite properties, this is not persisted as such
  422           this.loadPropertiesOverride(runtimeProps);
  423   
  424           // resolve boot delegation and system packages
  425           this.resolve(runtimeProps, "org.osgi.framework.bootdelegation",
  426               "sling.bootdelegation.");
  427           this.resolve(runtimeProps, "org.osgi.framework.system.packages",
  428               "sling.system.packages.");
  429   
  430           // reset back the sling home property
  431           // might have been overwritten by system properties, included
  432           // files or the sling.properties file
  433           staticProps.put(SharedConstants.SLING_HOME, slingHome);
  434           runtimeProps.put(SharedConstants.SLING_HOME, slingHome);
  435           runtimeProps.put(SLING_HOME_URL, slingHomeFile.toURI().toString());
  436   
  437           // add property file locations
  438           runtimeProps.put(SharedConstants.SLING_PROPERTIES, propFile.getAbsolutePath());
  439           try {
  440               runtimeProps.put(SharedConstants.SLING_PROPERTIES_URL, propFile.toURL().toString());
  441           } catch (MalformedURLException e) {
  442               // we simple ignore this
  443           }
  444   
  445           // Perform variable substitution for system properties.
  446           for (Entry<String, String> entry : runtimeProps.entrySet()) {
  447               entry.setValue(substVars(entry.getValue(), entry.getKey(), null,
  448                   runtimeProps));
  449           }
  450   
  451           // look for context:/ URLs to substitute
  452           for (Entry<String, String> entry : runtimeProps.entrySet()) {
  453               String name = entry.getKey();
  454               String value = entry.getValue();
  455               if (value != null && value.startsWith("context:/")) {
  456                   String path = value.substring("context:/".length() - 1);
  457   
  458                   InputStream src = this.resourceProvider.getResourceAsStream(path);
  459                   if (src != null) {
  460                       File target = new File(slingHome, path);
  461                       OutputStream dest = null;
  462                       try {
  463                           // only copy file if not existing
  464                           if (!target.exists()) {
  465                               target.getParentFile().mkdirs();
  466                               dest = new FileOutputStream(target);
  467                               byte[] buf = new byte[2048];
  468                               int rd;
  469                               while ((rd = src.read(buf)) >= 0) {
  470                                   dest.write(buf, 0, rd);
  471                               }
  472                           }
  473   
  474                           // after copying replace property and add url property
  475                           entry.setValue(target.getAbsolutePath());
  476   
  477                           // also set the new property on the unsubstituted props
  478                           staticProps.put(name, "${sling.home}" + path);
  479   
  480                       } catch (IOException ioe) {
  481                           this.logger.log(Logger.LOG_ERROR, "Cannot copy file "
  482                               + value + " to " + target, ioe);
  483                       } finally {
  484                           if (dest != null) {
  485                               try {
  486                                   dest.close();
  487                               } catch (IOException ignore) {
  488                               }
  489                           }
  490                           try {
  491                               src.close();
  492                           } catch (IOException ignore) {
  493                           }
  494                       }
  495                   }
  496               }
  497           }
  498   
  499           // write the unsubstituted properties back to the overlay file
  500           OutputStream os = null;
  501           try {
  502               // ensure parent folder(s)
  503               propFile.getParentFile().mkdirs();
  504   
  505               os = new FileOutputStream(propFile);
  506   
  507               // copy the values into a temporary properties structure to store
  508               Properties tmp = new Properties();
  509               tmp.putAll(staticProps);
  510               tmp.store(os, "Overlay properties for configuration");
  511           } catch (Exception ex) {
  512               this.logger.log(Logger.LOG_ERROR,
  513                   "Error loading overlay properties from " + propFile, ex);
  514           } finally {
  515               if (os != null) {
  516                   try {
  517                       os.close();
  518                   } catch (IOException ex2) {
  519                       // Nothing we can do.
  520                   }
  521               }
  522           }
  523   
  524           return runtimeProps;
  525       }
  526   
  527       /**
  528        * Scans the properties for any properties starting with the given
  529        * <code>prefix</code> (e.g. <code>sling.bootdelegation.</code>).
  530        * <ol>
  531        * <li>Each such property is checked, whether it actually starts with
  532        * <code>prefix<b>class.</b></code>. If so, the rest of the property
  533        * name is assumed to be a fully qualified class name which is check,
  534        * whether it is visible. If so, the value of the property is appended to
  535        * the value of the <code>osgiProp</code>. If the class cannot be loaded,
  536        * the property is ignored.
  537        * <li>Otherwise, if the property does not contain a fully qualified class
  538        * name, the value of the property is simply appended to the
  539        * <code>osgiProp</code>.
  540        * </ol>
  541        *
  542        * @param props The <code>Properties</code> to be scanned.
  543        * @param osgiProp The name of the property in <code>props</code> to which
  544        *            any matching property values are appended.
  545        * @param prefix The prefix of properties to handle.
  546        */
  547       private void resolve(Map<String, String> props, String osgiProp,
  548               String prefix) {
  549           final String propVal = props.get(osgiProp);
  550           StringBuffer prop = new StringBuffer(propVal == null ? "" : propVal);
  551           boolean mod = false;
  552           for (Entry<String, String> pEntry : props.entrySet()) {
  553               String key = pEntry.getKey();
  554               if (key.startsWith(prefix)) {
  555                   if (key.indexOf("class.") == prefix.length()) {
  556                       // prefix is followed by checker class name
  557                       String className = key.substring(prefix.length()
  558                           + "class.".length());
  559                       try {
  560                           Class.forName(className, true,
  561                               this.getClass().getClassLoader());
  562                       } catch (Throwable t) {
  563                           // don't really care, but class checking failed, so we
  564                           // do not add
  565                           this.logger.log(Logger.LOG_DEBUG, "Class " + className
  566                               + " not found. Ignoring '" + pEntry.getValue()
  567                               + "' for property " + osgiProp);
  568                           continue;
  569                       }
  570                   }
  571   
  572                   // get here if class is known or no checker class
  573                   this.logger.log(Logger.LOG_DEBUG, "Adding '"
  574                       + pEntry.getValue() + "' to property " + osgiProp);
  575                   if (prop.length() > 0) {
  576                       prop.append(',');
  577                   }
  578                   prop.append(pEntry.getValue());
  579                   mod = true;
  580               }
  581           }
  582   
  583           // replace the property with the modified property
  584           if (mod) {
  585               this.logger.log(Logger.LOG_DEBUG, "Setting property " + osgiProp
  586                   + " to " + prop.toString());
  587               props.put(osgiProp, prop.toString());
  588           }
  589       }
  590   
  591       /**
  592        * Converts an old Felix framework property into a new (standard or modified
  593        * Felix framework) property. If a property named <code>oldName</code> does
  594        * not exist in the <code>props</code> map, the map is not modified. If such
  595        * a property exists it is removed and add to the map with the
  596        * <code>newName</code> key. If both properties <code>oldName</code> and
  597        * <code>newName</code> exist, the property <code>newName</code> is replaced
  598        * with the value of the property <code>oldName</code>.
  599        *
  600        * @param props The map of properties containing the property to rename
  601        * @param oldName The old key of the property value
  602        * @param newName The new key of the property value
  603        */
  604       private void migrateProp(Map<String, String> props, String oldName,
  605               String newName) {
  606           String propValue = props.remove(oldName);
  607           if (propValue != null) {
  608               String previousNewValue = props.put(newName, propValue);
  609               if (previousNewValue != null) {
  610                   logger.log(Logger.LOG_WARNING, "Old value (" + previousNewValue
  611                       + ") of property " + newName + " by value: " + propValue);
  612               } else {
  613                   logger.log(Logger.LOG_INFO, "Property " + oldName + " ("
  614                       + propValue + ") renamed to " + newName);
  615               }
  616           } else {
  617               logger.log(Logger.LOG_DEBUG, "Property " + oldName
  618                   + " does not exist, nothing to do");
  619           }
  620       }
  621   
  622       private void setInstallBundles(Map<String, String> props) {
  623           String prefix = "sling.install.";
  624           Set<String> levels = new TreeSet<String>();
  625           for (String key : props.keySet()) {
  626               if (key.startsWith(prefix)) {
  627                   levels.add(key.substring(prefix.length()));
  628               }
  629           }
  630   
  631           StringBuffer buf = new StringBuffer();
  632           for (String level : levels) {
  633               if (buf.length() > 0) {
  634                   buf.append(',');
  635               }
  636               buf.append(level);
  637           }
  638   
  639           props.put(prefix + "bundles", buf.toString());
  640       }
  641   
  642       /**
  643        * Sets the correct execution environment values for the Java Runtime in
  644        * which Sling is runnin. This method takes any
  645        * <code>org.osgi.framework.executionenvironment</code> property already set
  646        * and ensures the older settings are included. If the property is not set
  647        * yet, it is fully constructed by this method.
  648        *
  649        * @param props The configuration properties to check and optionally ammend.
  650        */
  651       private void setExecutionEnvironment(Map<String, String> props) {
  652           // get the current Execution Environment setting
  653           String ee = props.get(Constants.FRAMEWORK_EXECUTIONENVIRONMENT);
  654           if (ee == null) {
  655               ee = System.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT);
  656           }
  657   
  658           // prepare for building the new property value
  659           StringBuilder eebuilder = new StringBuilder();
  660           if (ee != null) {
  661               eebuilder.append(ee);
  662           }
  663   
  664           // get the minor Java versions (expected to be 5, 6, or higher)
  665           int javaMinor;
  666           try {
  667               String specVString = System.getProperty("java.specification.version");
  668               javaMinor = Version.parseVersion(specVString).getMinor();
  669           } catch (IllegalArgumentException iae) {
  670               // don't care, assume minimal sling version (1.5)
  671               javaMinor = 5;
  672           }
  673   
  674           // walk the list of known names and include any by java minor version
  675           for (int i = 0; i < javaMinor && i < EE_NAMES.length; i++) {
  676               String[] vmEENames = EE_NAMES[i];
  677               for (String vmEEName : vmEENames) {
  678                   if (eebuilder.indexOf(vmEEName) < 0) {
  679                       if (eebuilder.length() > 0) {
  680                           eebuilder.append(',');
  681                       }
  682                       eebuilder.append(vmEEName);
  683                   }
  684               }
  685           }
  686   
  687           // finally set the new execution environment value
  688           ee = eebuilder.toString();
  689           this.logger.log(Logger.LOG_INFO,
  690               "Using Execution Environment setting: " + ee);
  691           props.put(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, ee);
  692       }
  693   
  694       // ---------- Extension support --------------------------------------------
  695   
  696       /**
  697        * Loads additional properties into the <code>properties</code> object.
  698        * <p>
  699        * This implementation does nothing and may be overwritten by extensions
  700        * requiring additional properties to be set.
  701        * <p>
  702        * This method is called when the servlet is initialized to prepare the
  703        * configuration for <code>Felix</code>. Implementations may add
  704        * properties from implementation specific sources. Properties added here
  705        * overwrite properties loaded from the default properties file and may be
  706        * overwritten by parameters set in the web application.
  707        * <p>
  708        * The <code>properties</code> object has not undergone variable
  709        * substition and properties added by this method may also contain values
  710        * refererring to other properties.
  711        * <p>
  712        * The properties added in this method will not be persisted in the
  713        * <code>sling.properties</code> file in the <code>sling.home</code>
  714        * directory.
  715        *
  716        * @param properties The <code>Properties</code> object to which custom
  717        *            properties may be added.
  718        */
  719       protected void loadPropertiesOverride(Map<String, String> properties) {
  720       }
  721   
  722       /**
  723        * Returns the <code>BundleContext</code> of the system bundle of the OSGi
  724        * framework launched by this servlet. This method only returns a non-<code>null</code>
  725        * object after the system bundle of the framework has been started and
  726        * before it is being stopped.
  727        */
  728       protected final BundleContext getBundleContext() {
  729           return this.bundleContext;
  730       }
  731   
  732       /**
  733        * Executes additional startup tasks and is called by the
  734        * {@link #start(BundleContext)} method.
  735        * <p>
  736        * This implementation does nothing and may be overwritten by extensions
  737        * requiring additional startup tasks.
  738        *
  739        * @throws Exception May be thrown in case of problems.
  740        */
  741       protected void doStartBundle() throws Exception {
  742       }
  743   
  744       /**
  745        * Executes additional shutdown tasks and is called by the
  746        * {@link #stop(BundleContext)} method.
  747        * <p>
  748        * This implementation does nothing and may be overwritten by extensions
  749        * requiring additional shutdown tasks.
  750        * <p>
  751        * When overwriting this method, it must be made sure, that no exception may
  752        * be thrown, otherwise unexpected behaviour may result.
  753        */
  754       protected void doStopBundle() {
  755       }
  756   
  757       // ---------- Property file support ----------------------------------------
  758   
  759       /**
  760        * Looks for <code>sling.include</code> and <code>sling.include.*</code>
  761        * properties in the <code>props</code> and loads properties form the
  762        * respective locations.
  763        * <p>
  764        * Each <code>sling.include</code> (or <code>sling.include.*</code>)
  765        * property may contain a comma-separated list of resource and/or file names
  766        * to be loaded from. The includes are loaded in alphabetical order of the
  767        * property names.
  768        * <p>
  769        * Each reasource path is first tried to be loaded through the
  770        * {@link #resourceProvider}. If that fails, the resource path is tested as
  771        * a file. If relative <code>slingHome</code> is used as the parent if not
  772        * <code>null</code>, otherwise the current working directory is used as
  773        * the parent.
  774        * <p>
  775        * Any non-existing resource is silently ignored.
  776        * <p>
  777        * When the method returns, the <code>sling.include</code> and
  778        * <code>sling.include.*</code> properties are not contained in the
  779        * <code>props</code> any more.
  780        *
  781        * @param props The <code>Properties</code> containing the
  782        *            <code>sling.include</code> and <code>sling.include.*</code>
  783        *            properties. This is also the destination for the new
  784        *            properties loaded.
  785        * @param slingHome The parent directory used to resolve relative path names
  786        *            if loading from a file. This may be <code>null</code> in
  787        *            which case the current working directory is used as the
  788        *            parent.
  789        */
  790       private void loadIncludes(Map<String, String> props, String slingHome) {
  791           // Build the sort map of include properties first
  792           // and remove include elements from the properties
  793           SortedMap<String, String> includes = new TreeMap<String, String>();
  794           for (Iterator<Entry<String, String>> pi = props.entrySet().iterator(); pi.hasNext();) {
  795               Entry<String, String> entry = pi.next();
  796               if (entry.getKey().startsWith("sling.include.")
  797                   || entry.getKey().equals("sling.include")) {
  798                   includes.put(entry.getKey(), entry.getValue());
  799                   pi.remove();
  800               }
  801           }
  802   
  803           for (Iterator<Entry<String, String>> ii = includes.entrySet().iterator(); ii.hasNext();) {
  804               Map.Entry<String, String> entry = ii.next();
  805               String key = entry.getKey();
  806               String include = entry.getValue();
  807   
  808               // ensure variable resolution on this property
  809               include = substVars(include, key, null, props);
  810   
  811               StringTokenizer tokener = new StringTokenizer(include, ",");
  812               while (tokener.hasMoreTokens()) {
  813                   String file = tokener.nextToken().trim();
  814                   InputStream is = this.resourceProvider.getResourceAsStream(file);
  815                   try {
  816                       if (is == null && slingHome != null) {
  817                           File resFile = new File(file);
  818                           if (!resFile.isAbsolute()) {
  819                               resFile = new File(slingHome, file);
  820                           }
  821                           if (resFile.canRead()) {
  822                               is = new FileInputStream(resFile);
  823                               file = resFile.getAbsolutePath(); // for logging
  824                           }
  825                       }
  826   
  827                       if (is != null) {
  828                           this.load(props, is);
  829                       }
  830                   } catch (IOException ioe) {
  831                       this.logger.log(Logger.LOG_ERROR,
  832                           "Error loading config properties from " + file, ioe);
  833                   }
  834               }
  835           }
  836       }
  837   
  838       /**
  839        * Load properties from the given resource file, which is accessed through
  840        * the {@link #resourceProvider}. If the resource does not exist, nothing
  841        * is loaded.
  842        *
  843        * @param props The <code>Properties</code> into which the loaded
  844        *            properties are loaded
  845        * @param resource The resource from which to load the resources
  846        */
  847       private void load(Map<String, String> props, String resource) {
  848           InputStream is = this.resourceProvider.getResourceAsStream(resource);
  849           if (is != null) {
  850               try {
  851                   this.load(props, is);
  852               } catch (IOException ioe) {
  853                   this.logger.log(Logger.LOG_ERROR,
  854                       "Error loading config properties from " + resource, ioe);
  855               }
  856           }
  857       }
  858   
  859       /**
  860        * Load properties from the given file. If the resource cannot be read from
  861        * (e.g. because it does not exist), nothing is loaded.
  862        *
  863        * @param props The <code>Properties</code> into which the loaded
  864        *            properties are loaded
  865        * @param file The <code>File</code> to load the properties from
  866        */
  867       private void load(Map<String, String> props, File file) {
  868           if (file != null && file.canRead()) {
  869               try {
  870                   this.load(props, new FileInputStream(file));
  871               } catch (IOException ioe) {
  872                   this.logger.log(Logger.LOG_ERROR,
  873                       "Error loading config properties from "
  874                           + file.getAbsolutePath(), ioe);
  875               }
  876           }
  877       }
  878   
  879       private void load(Map<String, String> props, InputStream ins)
  880               throws IOException {
  881           try {
  882               Properties tmp = new Properties();
  883               tmp.load(ins);
  884   
  885               for (Map.Entry<Object, Object> entry : tmp.entrySet()) {
  886                   props.put((String) entry.getKey(), (String) entry.getValue());
  887               }
  888           } finally {
  889               try {
  890                   ins.close();
  891               } catch (IOException ioe2) {
  892                   // ignore
  893               }
  894           }
  895       }
  896   
  897       // ---------- Property file variable substition support --------------------
  898   
  899       /**
  900        * The starting delimiter of variable names (value is "${").
  901        */
  902       private static final String DELIM_START = "${";
  903   
  904       /**
  905        * The ending delimiter of variable names (value is "}").
  906        */
  907       private static final String DELIM_STOP = "}";
  908   
  909       /**
  910        * This method performs property variable substitution on the specified
  911        * value. If the specified value contains the syntax
  912        * <tt>${&lt;prop-name&gt;}</tt>, where <tt>&lt;prop-name&gt;</tt>
  913        * refers to either a configuration property or a system property, then the
  914        * corresponding property value is substituted for the variable placeholder.
  915        * Multiple variable placeholders may exist in the specified value as well
  916        * as nested variable placeholders, which are substituted from inner most to
  917        * outer most. Configuration properties override system properties.
  918        *
  919        * @param val The string on which to perform property substitution.
  920        * @param currentKey The key of the property being evaluated used to detect
  921        *            cycles.
  922        * @param cycleMap Map of variable references used to detect nested cycles.
  923        * @param configProps Set of configuration properties.
  924        * @return The value of the specified string after system property
  925        *         substitution.
  926        * @throws IllegalArgumentException If there was a syntax error in the
  927        *             property placeholder syntax or a recursive variable
  928        *             reference.
  929        */
  930       private static String substVars(String val, String currentKey,
  931               Map<String, String> cycleMap, Map<String, String> configProps)
  932               throws IllegalArgumentException {
  933           // If there is currently no cycle map, then create
  934           // one for detecting cycles for this invocation.
  935           if (cycleMap == null) {
  936               cycleMap = new HashMap<String, String>();
  937           }
  938   
  939           // Put the current key in the cycle map.
  940           cycleMap.put(currentKey, currentKey);
  941   
  942           // Assume we have a value that is something like:
  943           // "leading ${foo.${bar}} middle ${baz} trailing"
  944   
  945           // Find the first ending '}' variable delimiter, which
  946           // will correspond to the first deepest nested variable
  947           // placeholder.
  948           int stopDelim = val.indexOf(DELIM_STOP);
  949   
  950           // Find the matching starting "${" variable delimiter
  951           // by looping until we find a start delimiter that is
  952           // greater than the stop delimiter we have found.
  953           int startDelim = val.indexOf(DELIM_START);
  954           while (stopDelim >= 0) {
  955               int idx = val.indexOf(DELIM_START, startDelim
  956                   + DELIM_START.length());
  957               if ((idx < 0) || (idx > stopDelim)) {
  958                   break;
  959               } else if (idx < stopDelim) {
  960                   startDelim = idx;
  961               }
  962           }
  963   
  964           // If we do not have a start or stop delimiter, then just
  965           // return the existing value.
  966           if ((startDelim < 0) && (stopDelim < 0)) {
  967               return val;
  968           }
  969           // At this point, we found a stop delimiter without a start,
  970           // so throw an exception.
  971           else if (((startDelim < 0) || (startDelim > stopDelim))
  972               && (stopDelim >= 0)) {
  973               throw new IllegalArgumentException(
  974                   "stop delimiter with no start delimiter: " + val);
  975           }
  976   
  977           // At this point, we have found a variable placeholder so
  978           // we must perform a variable substitution on it.
  979           // Using the start and stop delimiter indices, extract
  980           // the first, deepest nested variable placeholder.
  981           String variable = val.substring(startDelim + DELIM_START.length(),
  982               stopDelim);
  983   
  984           // Verify that this is not a recursive variable reference.
  985           if (cycleMap.get(variable) != null) {
  986               throw new IllegalArgumentException("recursive variable reference: "
  987                   + variable);
  988           }
  989   
  990           // Get the value of the deepest nested variable placeholder.
  991           // Try to configuration properties first.
  992           String substValue = (configProps != null)
  993                   ? configProps.get(variable)
  994                   : null;
  995           if (substValue == null) {
  996               // Ignore unknown property values.
  997               substValue = System.getProperty(variable, "");
  998           }
  999   
 1000           // Remove the found variable from the cycle map, since
 1001           // it may appear more than once in the value and we don't
 1002           // want such situations to appear as a recursive reference.
 1003           cycleMap.remove(variable);
 1004   
 1005           // Append the leading characters, the substituted value of
 1006           // the variable, and the trailing characters to get the new
 1007           // value.
 1008           val = val.substring(0, startDelim) + substValue
 1009               + val.substring(stopDelim + DELIM_STOP.length(), val.length());
 1010   
 1011           // Now perform substitution again, since there could still
 1012           // be substitutions to make.
 1013           val = substVars(val, currentKey, cycleMap, configProps);
 1014   
 1015           // Return the value.
 1016           return val;
 1017       }
 1018   }

Save This Page
Home » org.apache.sling.launchpad.base-2.2.0-source-release » org.apache.sling.launchpad.base.impl » [javadoc | source]