Save This Page
Home » org.apache.sling.launchpad.base-2.2.0-source-release » org.apache.sling.launchpad.base.webapp » [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.webapp;
   18   
   19   import static org.apache.felix.framework.util.FelixConstants.LOG_LEVEL_PROP;
   20   
   21   import java.io.IOException;
   22   import java.net.MalformedURLException;
   23   import java.net.URL;
   24   import java.util.Collections;
   25   import java.util.Enumeration;
   26   import java.util.HashMap;
   27   import java.util.Iterator;
   28   import java.util.Map;
   29   import java.util.Set;
   30   
   31   import javax.servlet.GenericServlet;
   32   import javax.servlet.Servlet;
   33   import javax.servlet.ServletContext;
   34   import javax.servlet.ServletException;
   35   import javax.servlet.ServletRequest;
   36   import javax.servlet.ServletResponse;
   37   import javax.servlet.http.HttpServletResponse;
   38   
   39   import org.apache.felix.framework.Logger;
   40   import org.apache.felix.http.proxy.ProxyServlet;
   41   import org.apache.sling.launchpad.base.impl.ClassLoaderResourceProvider;
   42   import org.apache.sling.launchpad.base.impl.ResourceProvider;
   43   import org.apache.sling.launchpad.base.impl.Sling;
   44   import org.apache.sling.launchpad.base.shared.Launcher;
   45   import org.apache.sling.launchpad.base.shared.Notifiable;
   46   import org.apache.sling.launchpad.base.shared.SharedConstants;
   47   import org.osgi.framework.BundleException;
   48   import org.osgi.framework.ServiceReference;
   49   
   50   /**
   51    * The <code>SlingServletDelegate</code> serves as a basic servlet for Project Sling.
   52    * The tasks of this servlet are as follows:
   53    * <ul>
   54    * <li>The {@link #init()} method launches Apache <code>Felix</code> as the
   55    * OSGi framework implementation we use.
   56    * <li>Registers as a service listener interested for services of type
   57    * <code>javax.servlet.Servlet</code>.
   58    * <li>Handles requests by delegating to a servlet which is expected to be
   59    * registered with the framework as a service of type
   60    * <code>javax.servlet.Servlet</code>. If no delegatee servlet has been
   61    * registered request handlings results in a temporary unavailability of the
   62    * servlet.
   63    * </ul>
   64    * <p>
   65    * <b>Request Handling</b>
   66    * <p>
   67    * This servlet handles request by forwarding to a delegatee servlet. The
   68    * delegatee servlet is automatically retrieved from the service registry by the
   69    * {@link #getDelegatee()}. This method also makes sure, the such a servlet
   70    * actually exits by throwing an <code>UnvailableException</code> if not and
   71    * also makes sure the servlet is initialized.
   72    * <p>
   73    * <b>Launch Configuration</b>
   74    * <p>
   75    * The Apache <code>Felix</code> framework requires configuration parameters
   76    * to be specified for startup. This servlet builds the list of parameters from
   77    * three locations:
   78    * <ol>
   79    * <li>The <code>sling.properties</code> is read
   80    * from the servlet class path. This properties file contains default settings.</li>
   81    * <li>Extensions of this servlet may provide additional properties to be
   82    * loaded overwriting the {@link #loadConfigProperties(String)} method.
   83    * <li>Finally, web application init parameters are added to the properties and
   84    * may overwrite existing properties of the same name(s).
   85    * </ol>
   86    * <p>
   87    * After loading all properties, variable substitution takes place on the
   88    * property values. A variable is indicated as <code>${&lt;prop-name&gt;}</code>
   89    * where <code>&lt;prop-name&gt;</code> is the name of a system or
   90    * configuration property (configuration properties override system properties).
   91    * Variables may be nested and are resolved from inner-most to outer-most. For
   92    * example, the property value <code>${outer-${inner}}</code> is resolved by
   93    * first resolving <code>${inner}</code> and then resolving the property whose
   94    * name is the catenation of <code>outer-</code> and the result of resolving
   95    * <code>${inner}</code>.
   96    * <p>
   97    * <b>Logging</b>
   98    * <p>
   99    * This servlet logs through the servlet container logging mechanism by calling
  100    * the <code>GenericServlet.log</code> methods. Bundles launched within the
  101    * framework provided by this servlet may use whatever logging mechanism they
  102    * choose to use. The Commons OSGI Log Bundle provides an OSGi Log Service
  103    * implementation, which also provides access to Apache Commons Logging, SLF4J
  104    * and Log4J logging. It is recommended that this bundle is used to setup and
  105    * configure logging for systems based on this servlet.
  106    */
  107   public class SlingServletDelegate extends GenericServlet implements Launcher {
  108   
  109       /** Pseduo class version ID to keep the IDE quite. */
  110       private static final long serialVersionUID = 1L;
  111   
  112       /** Mapping between log level numbers and names */
  113       private static final String[] logLevels = { "FATAL", "ERROR", "WARN",
  114           "INFO", "DEBUG" };
  115   
  116       /**
  117        * The Sling configuration property name setting the initial log level
  118        * (corresponds to LogbackManager.LOG_LEVEL constant)
  119        */
  120       private static final String PROP_LOG_LEVEL = "org.apache.sling.commons.log.level";
  121   
  122       /**
  123        * The name of the configuration property defining the obr repository.
  124        */
  125       private static final String OBR_REPOSITORY_URL = "obr.repository.url";
  126   
  127       /**
  128        * Flag set by the {@link #destroy()} method to indicate the servlet has
  129        * been destroyed. This flag is used by the {@link #startSling(String)}
  130        * method to check whether the SlingServletDelegate has been destroyed while Sling
  131        * was starting up.
  132        */
  133       private boolean servletDestroyed = false;
  134   
  135       /**
  136        * The <code>Felix</code> instance loaded on {@link #init()} and stopped
  137        * on {@link #destroy()}.
  138        */
  139       private SlingBridge sling;
  140   
  141       /**
  142        * The map of delegatee servlets to which requests are delegated. This map
  143        * is managed through the
  144        * {@link #serviceChanged(ServiceEvent) service listener} based on servlets
  145        * registered.
  146        *
  147        * @see #getDelegatee()
  148        * @see #ungetDelegatee(Object)
  149        */
  150       private Servlet delegatee;
  151   
  152       private Notifiable notifiable;
  153   
  154       private String slingHome;
  155   
  156       public void setNotifiable(Notifiable notifiable) {
  157           this.notifiable = notifiable;
  158       }
  159   
  160       public void setCommandLine(Map<String, String> args) {
  161           // ignore this for now
  162       }
  163   
  164       public void setSlingHome(String slingHome) {
  165           this.slingHome = slingHome;
  166       }
  167   
  168       public boolean start() {
  169           // might want to log, why we don't start !
  170           return false;
  171       }
  172   
  173       public void stop() {
  174           destroy();
  175       }
  176   
  177      /**
  178        * Initializes this servlet by loading the framework configuration
  179        * properties, starting the OSGi framework (Apache Felix) and exposing the
  180        * system bundle context and the <code>Felix</code> instance as servlet
  181        * context attributes.
  182        *
  183        * @throws ServletException if the framework cannot be initialized.
  184        */
  185       public final void init() throws ServletException {
  186           // temporary holders control final setup and ensure proper
  187           // disposal in case of setup errors
  188           SlingBridge tmpSling = null;
  189           Servlet tmpDelegatee = null;
  190   
  191           try {
  192   
  193               log("Starting Sling in " + slingHome);
  194   
  195               // read the default parameters
  196               Map<String, String> props = loadConfigProperties(slingHome);
  197   
  198               Logger logger = new ServletContextLogger(getServletContext());
  199               ResourceProvider rp = new ServletContextResourceProvider(
  200                   getServletContext());
  201               tmpSling = new SlingBridge(notifiable, logger, rp, props, getServletContext());
  202   
  203               // set up the OSGi HttpService proxy servlet
  204               tmpDelegatee = new ProxyServlet();
  205               tmpDelegatee.init(getServletConfig());
  206   
  207               // set the fields only if the SlingServletDelegate has no been destroyed
  208               // while Sling has been starting up. Otherwise we do not set the
  209               // fields and leave the temporary variables assigned to have
  210               // them destroyed in the finally clause.
  211               if (servletDestroyed) {
  212   
  213                   log("SlingServletDelegate destroyed while starting Sling, shutting Sling down");
  214   
  215               } else {
  216   
  217                   // set the fields now
  218                   sling = tmpSling;
  219                   delegatee = tmpDelegatee;
  220   
  221                   // reset temporary holders to prevent destroyal
  222                   tmpSling = null;
  223                   tmpDelegatee = null;
  224   
  225                   log("Sling successfully started in " + slingHome);
  226               }
  227   
  228           } catch (BundleException be) {
  229   
  230               throw new ServletException("Failed to start Sling in " + slingHome, be);
  231   
  232           } catch (ServletException se) {
  233   
  234               throw new ServletException("Failed to start bridge servlet for Sling", se);
  235   
  236           } catch (Throwable t) {
  237   
  238               throw new ServletException("Uncaught Failure starting Sling", t);
  239   
  240           } finally {
  241   
  242               // clean up temporary fields
  243               if (tmpDelegatee != null) {
  244                   tmpDelegatee.destroy();
  245               }
  246               if (tmpSling != null) {
  247                   tmpSling.destroy();
  248               }
  249           }
  250       }
  251   
  252       /**
  253        * Services the request by delegating to the delegatee servlet. If no
  254        * delegatee servlet is available, a <code>UnavailableException</code> is
  255        * thrown.
  256        *
  257        * @param req the <code>ServletRequest</code> object that contains the
  258        *            client's request
  259        * @param res the <code>ServletResponse</code> object that will contain
  260        *            the servlet's response
  261        * @throws javax.servlet.UnavailableException if the no delegatee servlet is currently
  262        *             available
  263        * @throws ServletException if an exception occurs that interferes with the
  264        *             servlet's normal operation occurred
  265        * @throws IOException if an input or output exception occurs
  266        */
  267       public final void service(ServletRequest req, ServletResponse res)
  268               throws ServletException, IOException {
  269   
  270           // delegate the request to the registered delegatee servlet
  271           Servlet delegatee = getDelegatee();
  272           if (delegatee == null) {
  273               ((HttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
  274           } else {
  275               delegatee.service(req, res);
  276           }
  277       }
  278   
  279       /**
  280        * Destroys this servlet by shutting down the OSGi framework and hence the
  281        * delegatee servlet if one is set at all.
  282        */
  283       public final void destroy() {
  284   
  285           // set the destroyed flag to signal to the startSling method
  286           // that Sling should be terminated immediately
  287           servletDestroyed = true;
  288   
  289           // destroy the delegatee
  290           if (delegatee != null) {
  291               delegatee.destroy();
  292               delegatee = null;
  293           }
  294   
  295           // shutdown the Felix container
  296           if (sling != null) {
  297               sling.destroy();
  298               sling = null;
  299           }
  300   
  301           // finally call the base class destroy method
  302           super.destroy();
  303       }
  304   
  305       Servlet getDelegatee() {
  306           return delegatee;
  307       }
  308   
  309       // ---------- Configuration Loading ----------------------------------------
  310   
  311       /**
  312        * Loads the configuration properties in the configuration property file
  313        * associated with the framework installation; these properties are
  314        * accessible to the framework and to bundles and are intended for
  315        * configuration purposes. By default, the configuration property file is
  316        * located in the <tt>conf/</tt> directory of the Felix installation
  317        * directory and is called "<tt>config.properties</tt>". The
  318        * installation directory of Felix is assumed to be the parent directory of
  319        * the <tt>felix.jar</tt> file as found on the system class path property.
  320        * The precise file from which to load configuration properties can be set
  321        * by initializing the "<tt>felix.config.properties</tt>" system
  322        * property to an arbitrary URL.
  323        *
  324        * @param slingHome The value to be used as the "sling.home" property in the
  325        *            returned map. This parameter is expected to be non-<code>null</code>.
  326        * @return A <tt>Properties</tt> instance.
  327        */
  328       private Map<String, String> loadConfigProperties(String slingHome) {
  329           // The config properties file is either specified by a system
  330           // property or it is in the same directory as the Felix JAR file.
  331           // Try to load it from one of these places.
  332           Map<String, String> props = new HashMap<String, String>();
  333   
  334           // The following property must start with a comma!
  335           final String servletVersion = getServletContext().getMajorVersion() + "." +
  336                                         getServletContext().getMinorVersion();
  337           props.put(
  338               Sling.PROP_SYSTEM_PACKAGES,
  339               ",javax.servlet;javax.servlet.http;javax.servlet.resources; version=" + servletVersion);
  340   
  341           // prevent system properties from being considered
  342           props.put(Sling.SLING_IGNORE_SYSTEM_PROPERTIES, "true");
  343   
  344           // copy context init parameters
  345           @SuppressWarnings("unchecked")
  346           Enumeration<String> cpe = getServletContext().getInitParameterNames();
  347           while (cpe.hasMoreElements()) {
  348               String name = cpe.nextElement();
  349               props.put(name, getServletContext().getInitParameter(name));
  350           }
  351   
  352           // copy servlet init parameters
  353           @SuppressWarnings("unchecked")
  354           Enumeration<String> pe = getInitParameterNames();
  355           while (pe.hasMoreElements()) {
  356               String name = pe.nextElement();
  357               props.put(name, getInitParameter(name));
  358           }
  359   
  360           // ensure the Felix Logger loglevel matches the Sling log level
  361           checkLogSettings(props);
  362   
  363           // if the specified obr location is not a url and starts with a '/', we
  364           // assume that this location is inside the webapp and create the correct
  365           // full url
  366           final String repoLocation = props.get(OBR_REPOSITORY_URL);
  367           if (insideWebapp(repoLocation)) {
  368               final URL url = getUrl(repoLocation);
  369               // only if we get back a resource url, we update it
  370               if (url != null)
  371                   props.put(OBR_REPOSITORY_URL, url.toExternalForm());
  372           }
  373   
  374           // set sling home
  375           props.put(SharedConstants.SLING_HOME, slingHome);
  376   
  377           return props;
  378       }
  379   
  380       private void checkLogSettings(Map<String, String> props) {
  381           String logLevelString = props.get(PROP_LOG_LEVEL);
  382           if (logLevelString != null) {
  383               int logLevel = 1;
  384               try {
  385                   logLevel = Integer.parseInt(logLevelString);
  386               } catch (NumberFormatException nfe) {
  387                   // might be a loglevel name
  388                   for (int i=0; i < logLevels.length; i++) {
  389                       if (logLevels[i].equalsIgnoreCase(logLevelString)) {
  390                           logLevel = i;
  391                           break;
  392                       }
  393                   }
  394               }
  395               props.put(LOG_LEVEL_PROP, String.valueOf(logLevel));
  396           }
  397       }
  398   
  399       private boolean insideWebapp(String path) {
  400           return path != null && path.indexOf(":/") < 1 && path.startsWith("/");
  401       }
  402   
  403       private URL getUrl(String path) {
  404           try {
  405               return getServletContext().getResource(path);
  406           } catch (MalformedURLException e) {
  407               return null;
  408           }
  409       }
  410   
  411       private static class ServletContextLogger extends Logger {
  412           private ServletContext servletContext;
  413   
  414           private ServletContextLogger(ServletContext servletContext) {
  415               this.servletContext = servletContext;
  416           }
  417   
  418           @Override
  419           protected void doLog(ServiceReference sr, int level, String msg,
  420                   Throwable throwable) {
  421   
  422               // unwind throwable if it is a BundleException
  423               if ((throwable instanceof BundleException)
  424                   && (((BundleException) throwable).getNestedException() != null)) {
  425                   throwable = ((BundleException) throwable).getNestedException();
  426               }
  427   
  428               String s = (sr == null) ? null : "SvcRef " + sr;
  429               s = (s == null) ? msg : s + " " + msg;
  430               s = (throwable == null) ? s : s + " (" + throwable + ")";
  431   
  432               switch (level) {
  433                   case LOG_DEBUG:
  434                       servletContext.log("DEBUG: " + s);
  435                       break;
  436                   case LOG_ERROR:
  437                       if (throwable == null) {
  438                           servletContext.log("ERROR: " + s);
  439                       } else {
  440                           servletContext.log("ERROR: " + s, throwable);
  441                       }
  442                       break;
  443                   case LOG_INFO:
  444                       servletContext.log("INFO: " + s);
  445                       break;
  446                   case LOG_WARNING:
  447                       servletContext.log("WARNING: " + s);
  448                       break;
  449                   default:
  450                       servletContext.log("UNKNOWN[" + level + "]: " + s);
  451               }
  452           }
  453       }
  454   
  455       private static class ServletContextResourceProvider extends
  456               ClassLoaderResourceProvider {
  457   
  458           /**
  459            * The root folder for internal web application files (value is
  460            * "/WEB-INF/").
  461            */
  462           private static final String WEB_INF = "/WEB-INF";
  463   
  464           private ServletContext servletContext;
  465   
  466           private ServletContextResourceProvider(ServletContext servletContext) {
  467               super(SlingServletDelegate.class.getClassLoader());
  468               this.servletContext = servletContext;
  469           }
  470   
  471           @SuppressWarnings("unchecked")
  472           @Override
  473           public Iterator<String> getChildren(String path) {
  474               // ensure leading slash
  475               if (path.charAt(0) != '/') {
  476                   path = "/" + path;
  477               }
  478   
  479               Set resources = servletContext.getResourcePaths(path); // unchecked
  480               if (resources == null || resources.isEmpty()) {
  481                   resources = servletContext.getResourcePaths(WEB_INF + path); // unchecked
  482               }
  483   
  484               Iterator resourceIterator;
  485               if ( resources == null || resources.isEmpty() ) {
  486                   // fall back to the class path
  487                   resourceIterator = super.getChildren(path);
  488   
  489                   if(resourceIterator.hasNext()) {
  490                       return resourceIterator;
  491                   }
  492   
  493                   // fall back to WEB-INF within the class path
  494                   resourceIterator = super.getChildren(WEB_INF + path);
  495   
  496                   if(resourceIterator.hasNext()) {
  497                       return resourceIterator;
  498                   }
  499               }
  500   
  501               if ( resources == null ) {
  502                   return Collections.EMPTY_LIST.iterator();
  503               }
  504               return resources.iterator(); // unchecked
  505           }
  506   
  507           public URL getResource(String path) {
  508               // nothing for empty or null path
  509               if (path == null || path.length() == 0) {
  510                   return null;
  511               }
  512   
  513               // ensure leading slash
  514               if (path.charAt(0) != '/') {
  515                   path = "/" + path;
  516               }
  517   
  518               try {
  519                   // try direct path
  520                   URL resource = servletContext.getResource(path);
  521                   if (resource != null) {
  522                       return resource;
  523                   }
  524   
  525                   // otherwise try WEB-INF location
  526                   resource = servletContext.getResource(WEB_INF + path);
  527                   if(resource != null) {
  528                       return resource;
  529                   }
  530   
  531                   // try classpath
  532                   resource = super.getResource(path);
  533                   if(resource != null) {
  534                       return resource;
  535                   }
  536   
  537                   // try WEB-INF within the classpath
  538                   resource = super.getResource(WEB_INF + path);
  539                   if(resource != null) {
  540                       return resource;
  541                   }
  542   
  543               } catch (MalformedURLException mue) {
  544                   servletContext.log("Failure to get resource " + path, mue);
  545               }
  546   
  547               // fall back to no resource found
  548               return null;
  549           }
  550   
  551       }
  552   }

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