001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.plugin;
021    
022    import org.crsh.util.ServletContextMap;
023    import org.crsh.util.Utils;
024    import org.crsh.vfs.spi.FSMountFactory;
025    import org.crsh.vfs.spi.file.FileMountFactory;
026    import org.crsh.vfs.spi.servlet.WarMountFactory;
027    import org.crsh.vfs.spi.url.ClassPathMountFactory;
028    
029    import javax.servlet.ServletContext;
030    import javax.servlet.ServletContextEvent;
031    import javax.servlet.ServletContextListener;
032    import java.util.HashMap;
033    import java.util.Map;
034    import java.util.logging.Level;
035    
036    public class WebPluginLifeCycle extends Embedded implements ServletContextListener {
037    
038      /** . */
039      private static final Object lock = new Object();
040    
041      /** . */
042      private static final Map<String, PluginContext> contextMap = new HashMap<String, PluginContext>();
043    
044      /** . */
045      private boolean registered = false;
046    
047      /** . */
048      private Map<String, FSMountFactory<?>> mountContexts = new HashMap<String, FSMountFactory<?>>(3);
049    
050      /** . */
051      private ServletContext context;
052    
053      /**
054       * Returns a plugin context associated with the servlet context or null if such context does not exist.
055       *
056       * @param contextPath the context path
057       * @return the associated plugin context
058       * @throws NullPointerException if the servlet context argument is null
059       */
060      public static PluginContext getPluginContext(String contextPath) throws NullPointerException {
061        synchronized (lock) {
062          return contextMap.get(contextPath);
063        }
064      }
065    
066      /**
067       * This implementation register three file system drivers:
068       * <ul>
069       *   <li><code>file</code> : the current file system</li>
070       *   <li><code>classpath</code> : the classpath</li>
071       *   <li><code>war</code> : the war content</li>
072       * </ul>
073       *
074       * @return the drivers
075       */
076      @Override
077      protected Map<String, FSMountFactory<?>> getMountFactories() {
078        return mountContexts;
079      }
080    
081      /**
082       * Create the service loader discovery, this can be subclassed to provide an implementation, the current
083       * implementation returns a {@link ServiceLoaderDiscovery} instance.
084       *
085       * @param context the servlet context
086       * @param classLoader the class loader
087       * @return the plugin discovery
088       */
089      protected PluginDiscovery createDiscovery(ServletContext context, ClassLoader classLoader) {
090        return new ServiceLoaderDiscovery(classLoader);
091      }
092    
093      public void contextInitialized(ServletContextEvent sce) {
094        context = sce.getServletContext();
095    
096        // Use JVM properties as external config
097        setConfig(System.getProperties());
098    
099        // Initialise the registerable drivers
100        try {
101          mountContexts.put("classpath", new ClassPathMountFactory(context.getClassLoader()));
102          mountContexts.put("file", new FileMountFactory(Utils.getCurrentDirectory()));
103          mountContexts.put("war", new WarMountFactory(context));
104        }
105        catch (Exception e) {
106          log.log(Level.SEVERE, "Coult not initialize classpath driver", e);
107          return;
108        }
109    
110        //
111        String contextPath = context.getContextPath();
112        synchronized (lock) {
113          if (!contextMap.containsKey(contextPath)) {
114            ClassLoader webAppLoader = Thread.currentThread().getContextClassLoader();
115            PluginDiscovery discovery = createDiscovery(context, webAppLoader);
116            PluginContext pluginContext = start(new ServletContextMap(context), discovery, context.getClassLoader());
117            contextMap.put(contextPath, pluginContext);
118            registered = true;
119          }
120        }
121      }
122    
123      /**
124       * The path property is resolved from the servlet context parameters. When the parameter does not exist,
125       * the <code>defaultValue</code> argument is used instead, so it should not be null.
126       * After the path is resolved, it is interpolated using the JVM system properties and the syntax
127       * defined by the {@link org.crsh.util.Utils#interpolate(String, java.util.Map)} function.
128       *
129       * @param propertyName the property name to resolve
130       * @param defaultValue the default property value
131       * @return the path value
132       */
133      private String resolvePathProperty(String propertyName, String defaultValue) {
134        String path = context.getInitParameter(propertyName);
135        if (path == null) {
136          path = defaultValue;
137        }
138        return Utils.interpolate(path, System.getProperties());
139      }
140    
141      /**
142       * @return the value returned by {@link #resolvePathProperty(String, String)} with the <code>crash.mountpointconfig.conf</code> name
143       *         and the {@link #getDefaultConfMountPointConfig()} default value
144       */
145      @Override
146      protected String resolveConfMountPointConfig() {
147        return resolvePathProperty("crash.mountpointconfig.conf", getDefaultConfMountPointConfig());
148      }
149    
150      /**
151       * @return the value returned by {@link #resolvePathProperty(String, String)} with the <code>crash.mountpointconfig.cmd</code> name
152       *         and the {@link #getDefaultCmdMountPointConfig()} default value
153       */
154      @Override
155      protected String resolveCmdMountPointConfig() {
156        return resolvePathProperty("crash.mountpointconfig.cmd", getDefaultCmdMountPointConfig());
157      }
158    
159      /**
160       * @return <code>war:/WEB-INF/crash/commands/</code>
161       */
162      protected String getDefaultCmdMountPointConfig() {
163        return "war:/WEB-INF/crash/commands/";
164      }
165    
166      /**
167       * @return <code>war:/WEB-INF/crash/</code>
168       */
169      protected String getDefaultConfMountPointConfig() {
170        return "war:/WEB-INF/crash";
171      }
172    
173      public void contextDestroyed(ServletContextEvent sce) {
174        if (registered) {
175    
176          //
177          ServletContext sc = sce.getServletContext();
178          String contextPath = sc.getContextPath();
179    
180          //
181          synchronized (lock) {
182    
183            //
184            contextMap.remove(contextPath);
185            registered = false;
186    
187            //
188            stop();
189          }
190        }
191      }
192    }