View Javadoc

1   /*
2    * Copyright 2011 FatWire Corporation. All Rights Reserved.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.fatwire.gst.foundation.controller.support;
17  
18  import java.io.BufferedReader;
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.lang.reflect.Constructor;
24  import java.lang.reflect.InvocationTargetException;
25  import java.net.URL;
26  import java.util.Enumeration;
27  import java.util.LinkedList;
28  import java.util.List;
29  
30  import javax.servlet.ServletContext;
31  import javax.servlet.ServletContextEvent;
32  import javax.servlet.ServletContextListener;
33  
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.commons.logging.Log;
36  
37  import com.fatwire.gst.foundation.controller.AppContext;
38  import com.fatwire.gst.foundation.controller.action.support.DefaultWebAppContext;
39  import com.fatwire.gst.foundation.facade.logging.LogUtil;
40  
41  /**
42   * ServletContextListener that loads and configures the AppContext for this
43   * application.
44   * 
45   * @author Dolf Dijkstra
46   * 
47   */
48  public class WebAppContextLoader implements ServletContextListener {
49      private static final String GROOVY_WEB_CONTEXT = "com.fatwire.gst.foundation.groovy.context.GroovyWebContext";
50      private static final String GROOVY_CLASSNAME = "groovy.util.GroovyScriptEngine";
51      public static final String CONTEXTS = "gsf-contexts";
52  
53      protected static final Log LOG = LogUtil.getLog(WebAppContextLoader.class);
54      boolean booted = false;
55      private static final Class<?>[] ARGS = new Class[] { ServletContext.class, AppContext.class };
56  
57      @Override
58      public void contextInitialized(final ServletContextEvent sce) {
59          final ServletContext context = sce.getServletContext();
60          if (context.getMajorVersion() == 2 && context.getMinorVersion() < 4) {
61              throw new IllegalStateException(
62                      "Servlet Container is configured for version 2.3 or less. This ServletContextListener does not support 2.3 and earlier as the load order of Listeners is not guaranteed.");
63          }
64  
65          configureWebAppContext(context);
66  
67      }
68  
69      public AppContext configureWebAppContext(final ServletContext context) {
70          AppContext parent = null;
71  
72          final ClassLoader cl = Thread.currentThread().getContextClassLoader();
73  
74          parent = configureFromInitParam(context, cl);
75          if (parent == null) {
76              try {
77                  parent = configureFromServiceLocator(context, cl);
78              } catch (IOException e) {
79                  LOG.debug("Exception when loadding the service descriptor for the AppContext from the classpath.", e);
80              }
81          }
82          if (parent == null) {
83              // if gsf-groovy is found and groovy classes around found, boot with
84              // groovy
85              final String groovyPath = context.getRealPath("/WEB-INF/gsf-groovy");
86  
87              if (new File(groovyPath).isDirectory() && isGroovyOnClassPath(cl)) {
88                  try {
89                      parent = createAppContext(cl, GROOVY_WEB_CONTEXT, context, null);
90                  } catch (final Exception e) {
91                      LOG.warn("Exception when creating the GroovyWebContext as a default option", e);
92                  }
93              }
94          }
95          if (parent == null) {
96              parent = new DefaultWebAppContext(context);
97              parent.init();
98          }
99  
100         if (parent != null) {
101             context.setAttribute(WebAppContext.WEB_CONTEXT_NAME, parent);
102         }
103         booted = true;
104         return parent;
105 
106     }
107 
108     private static final String PREFIX = "META-INF/";
109 
110     private AppContext configureFromServiceLocator(ServletContext context, ClassLoader cl) throws IOException {
111         String fullName = PREFIX + CONTEXTS;
112 
113         int c = 0;
114         List<String> init = new LinkedList<String>();
115         Enumeration<URL> configs = cl.getResources(fullName);
116         while (configs.hasMoreElements()) {
117 
118             URL u = configs.nextElement();
119             if (c++ > 0)
120                 throw new IllegalStateException("Found second service locator in classpath at " + u
121                         + ". Please make sure that only one " + fullName
122                         + " file is found on the classpath or configure the AppContext through web.xml");
123             InputStream in = null;
124             BufferedReader r = null;
125 
126             try {
127                 in = u.openStream();
128                 r = new BufferedReader(new InputStreamReader(in, "utf-8"));
129                 String s = null;
130                 while ((s = r.readLine()) != null) {
131                     if (StringUtils.isNotBlank(s) && !StringUtils.startsWith(s, "#")) {
132                         init.add(s);
133                     }
134                 }
135             } catch (IOException e) {
136                 throw new RuntimeException("Error reading configuration file", e);
137             } finally {
138                 try {
139                     if (r != null)
140                         r.close();
141                     if (in != null)
142                         in.close();
143                 } catch (IOException e) {
144                     throw new RuntimeException("Error closing configuration file", e);
145                 }
146             }
147             return this.createFromString(context, cl, init.toArray(new String[init.size()]));
148         }
149 
150         return null;
151     }
152 
153     /**
154      * Creates a AppContext based on the init parameter {@link #CONTEXTS}.
155      * 
156      * @param context
157      * @param cl
158      * @return the AppContext as configured from the web app init parameter.
159      */
160     protected AppContext configureFromInitParam(final ServletContext context, final ClassLoader cl) {
161         final String init = context.getInitParameter(CONTEXTS);
162         AppContext parent = null;
163 
164         if (init != null) {
165             parent = createFromString(context, cl, init.split(","));
166         }
167         return parent;
168     }
169 
170     private AppContext createFromString(final ServletContext context, final ClassLoader cl, final String[] c) {
171         AppContext parent = null;
172 
173         if (c != null) {
174 
175             for (int i = c.length - 1; i >= 0; i--) {
176 
177                 try {
178                     final AppContext n = createAppContext(cl, c[i], context, parent);
179                     if (n != null) {
180                         parent = n;
181                     }
182                 } catch (final IllegalArgumentException e) {
183                     LOG.warn(e);
184                 } catch (final InstantiationException e) {
185                     LOG.warn(e);
186                 } catch (final IllegalAccessException e) {
187                     LOG.warn(e);
188                 } catch (final InvocationTargetException e) {
189                     LOG.warn(e);
190                 } catch (final SecurityException e) {
191                     LOG.warn(e);
192                 } catch (final NoSuchMethodException e) {
193                     LOG.warn(e);
194                 } catch (final ClassNotFoundException e) {
195                     LOG.warn(e);
196                 }
197 
198             }
199 
200         }
201         return parent;
202     }
203 
204     private boolean isGroovyOnClassPath(final ClassLoader cl) {
205         try {
206             cl.loadClass(GROOVY_CLASSNAME);
207         } catch (final ClassNotFoundException e) {
208             return false;
209         }
210         return true;
211     }
212 
213     @Override
214     public void contextDestroyed(final ServletContextEvent sce) {
215         sce.getServletContext().removeAttribute(WebAppContext.WEB_CONTEXT_NAME);
216     }
217 
218     /**
219      * @param cl the classloader to load the class.
220      * @param c the class name, class needs to implement a constructor with a
221      *            <tt>context</tt> and a <tt>parent</tt> {@link #ARGS}.
222      * @param context the web context
223      * @param parent parent AppContext, null is none
224      * @return the created AppContext
225      * @throws ClassNotFoundException
226      * @throws SecurityException
227      * @throws NoSuchMethodException
228      * @throws InstantiationException
229      * @throws IllegalAccessException
230      * @throws InvocationTargetException
231      */
232     AppContext createAppContext(final ClassLoader cl, final String c, final ServletContext context,
233             final AppContext parent) throws ClassNotFoundException, SecurityException, NoSuchMethodException,
234             InstantiationException, IllegalAccessException, InvocationTargetException {
235         @SuppressWarnings("unchecked")
236         final Class<AppContext> cls = (Class<AppContext>) cl.loadClass(c);
237         final Constructor<AppContext> ctr = cls.getConstructor(ARGS);
238         AppContext n;
239         n = ctr.newInstance(context, parent);
240         if (n != null) {
241             LOG.info("Creating AppContext from class " + c);
242             n.init();
243         }
244         return n;
245 
246     }
247 
248 }