2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

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  
18  package org.apache.shale.remoting.faces;
19  
20  import java.io.InputStream;
21  import java.lang.reflect.Method;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.ResourceBundle;
26  import javax.faces.FacesException;
27  import javax.faces.application.ViewHandler;
28  import javax.faces.context.FacesContext;
29  import javax.xml.parsers.DocumentBuilder;
30  import javax.xml.parsers.DocumentBuilderFactory;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.shale.remoting.Constants;
34  import org.apache.shale.remoting.Mapping;
35  import org.apache.shale.remoting.Mappings;
36  import org.apache.shale.remoting.Mechanism;
37  import org.apache.shale.remoting.Processor;
38  import org.apache.shale.remoting.impl.FilteringProcessor;
39  import org.apache.shale.remoting.impl.MappingImpl;
40  import org.apache.shale.remoting.impl.MappingsImpl;
41  import org.w3c.dom.Document;
42  import org.w3c.dom.Node;
43  import org.w3c.dom.NodeList;
44  
45  /***
46   * <p>Helper bean for accessing the {@link Mappings} instance for this
47   * application, creating it the first time if necessary.</p>
48   *
49   * @since 1.0.1
50   */
51  public class MappingsHelper {
52  
53  
54      // ------------------------------------------------------ Instance Variables
55  
56  
57      /***
58       * <p><code>ResourceBundle</code> containing our localized messages.</p>
59       */
60      private ResourceBundle bundle =
61              ResourceBundle.getBundle("org.apache.shale.remoting.Bundle");
62  
63  
64      /***
65       * <p>Log instance for this class.</p>
66       */
67      private transient Log log = null;
68  
69  
70      // ---------------------------------------------------------- Public Methods
71  
72  
73      /***
74       * <p>Return the {@link Mappings} instance for this web application,
75       * creating it if necessary.</p>
76       *
77       * @param context <code>FacesContext</code> for the current request
78       */
79      public Mappings getMappings(FacesContext context) {
80  
81          Mappings mappings = (Mappings)
82              context.getExternalContext().getApplicationMap().
83                  get(Constants.MAPPINGS_ATTR);
84          if (mappings == null) {
85              mappings = createMappings(context);
86              context.getExternalContext().getApplicationMap().
87                      put(Constants.MAPPINGS_ATTR, mappings);
88          }
89          return mappings;
90  
91      }
92  
93  
94      // --------------------------------------------------------- Private Methods
95  
96  
97      /***
98       * <p>Configure {@link Mapping} instances on the specified {@link Mappings}
99       * instance, for the specified context initialization parameter.</p>
100      *
101      * @param context <code>FacesContext</code> for the current request
102      * @param mappings {@link Mappings} instance being configured
103      * @param paramName Context initialization parameter name to process
104      * @param excludesName Context initialization parameter containing our
105      *  exclude patterns
106      * @param excludesDefault Default exclude patterns if none are configured
107      * @param includesName Context initialization parameter containing our
108      *  include patterns
109      * @param includesDefault Default include patterns if none are configured
110      * @param mechanism {@link Mechanism} to configure on created instances
111      * @param defaultValue Default value (if any) if not specified
112      *
113      * @exception FacesException if a new Mapping instance cannot be created
114      *  or configured
115      */
116     private void configureMappings(FacesContext context, Mappings mappings,
117                                    String paramName,
118                                    String excludesName, String excludesDefault,
119                                    String includesName, String includesDefault,
120                                    Mechanism mechanism, String defaultValue) {
121 
122         // Identify the Mapping implementation class to be used
123         Class clazz = MappingImpl.class;
124         String mappingClass =
125           context.getExternalContext().getInitParameter(Constants.MAPPING_CLASS);
126         if (mappingClass != null) {
127             try {
128                 clazz = loadClass(mappingClass);
129             } catch (Exception e) {
130                 throw new FacesException(e);
131             }
132         }
133 
134         // Acquire the context initialization parameter value (or default it)
135         String paramValue = context.getExternalContext().getInitParameter(paramName);
136         if (paramValue == null) {
137             paramValue = defaultValue;
138         }
139         if (paramValue == null) {
140             return;
141         }
142 
143         // Configure new Mapping instances for each specified pattern:classname pair
144         while (true) {
145             paramValue = paramValue.trim();
146             if (paramValue.length() == 0) {
147                 break;
148             }
149             String pair = null;
150             int comma = paramValue.indexOf(',');
151             if (comma >= 0) {
152                 pair = paramValue.substring(0, comma).trim();
153                 paramValue = paramValue.substring(comma + 1);
154             } else {
155                 pair = paramValue.trim();
156                 paramValue = "";
157             }
158             int colon = pair.indexOf(':');
159             if (colon < 0) {
160                 throw new IllegalArgumentException(pair);
161             }
162             String pattern = pair.substring(0, colon).trim();
163             String processorClass = pair.substring(colon + 1).trim();
164             if (log().isInfoEnabled()) {
165                 log().info(bundle.getString("mapping.configure"));
166                 log().info(pattern + ":" + processorClass);
167             }
168             Class processorClazz = null;
169             try {
170                 processorClazz = loadClass(processorClass);
171             } catch (Exception e) {
172                 throw new FacesException(e);
173             }
174             try {
175                 Mapping mapping = (Mapping) clazz.newInstance();
176                 mapping.setMappings(mappings);
177                 mapping.setMechanism(mechanism);
178                 mapping.setPattern(pattern);
179                 Processor processor = (Processor) processorClazz.newInstance();
180                 if (processor instanceof FilteringProcessor) {
181                     String excludesPatterns =
182                       context.getExternalContext().getInitParameter(excludesName);
183                     if (excludesPatterns == null) {
184                         excludesPatterns = excludesDefault;
185                     }
186                     ((FilteringProcessor) processor).setExcludes(excludesPatterns);
187                     String includesPatterns =
188                       context.getExternalContext().getInitParameter(includesName);
189                     if (includesPatterns == null) {
190                         includesPatterns = includesDefault;
191                     }
192                     ((FilteringProcessor) processor).setIncludes(includesPatterns);
193                 }
194                 mapping.setProcessor(processor);
195                 mappings.addMapping(mapping);
196             } catch (RuntimeException e) {
197                 throw e;
198             } catch (Exception e) {
199                 throw new FacesException(e);
200             }
201         }
202 
203     }
204 
205 
206     /***
207      * <p>Create and configure a {@link Mappings} instance based on the relevant
208      * context initialization parameters for this web application.</p>
209      *
210      * @param context <code>FacesContext</code> for the current request
211      *
212      * @exception FacesException if a new Mappings instance cannot be created
213      *  or configured
214      */
215     private Mappings createMappings(FacesContext context) {
216 
217         // Instantiate a Mappings instance to configure
218         Mappings mappings = null;
219         String mappingsClass = MappingsImpl.class.getName();
220         String mappingsClassParam =
221           context.getExternalContext().getInitParameter(Constants.MAPPINGS_CLASS);
222         if (mappingsClassParam != null) {
223             mappingsClass = mappingsClassParam;
224         }
225         Class clazz = null;
226         try {
227             if (log().isInfoEnabled()) {
228                 log().info(bundle.getString("mappings.configure"));
229                 log().info(mappingsClass);
230             }
231             clazz = loadClass(mappingsClass);
232         } catch (Exception e) {
233             throw new FacesException(e);
234         }
235         try {
236             mappings = (Mappings) clazz.newInstance();
237         } catch (Exception e) {
238             throw new FacesException(e);
239         }
240 
241         // Configure the Mapping instances for this Mappings instance
242         configureMappings(context, mappings, Constants.CLASS_RESOURCES_PARAM,
243                           Constants.CLASS_RESOURCES_EXCLUDES,
244                           Constants.CLASS_RESOURCES_EXCLUDES_DEFAULT,
245                           Constants.CLASS_RESOURCES_INCLUDES,
246                           Constants.CLASS_RESOURCES_INCLUDES_DEFAULT,
247                           Mechanism.CLASS_RESOURCE,
248                           "/static/*:org.apache.shale.remoting.impl.ClassResourceProcessor");
249         configureMappings(context, mappings, Constants.DYNAMIC_RESOURCES_PARAM,
250                           Constants.DYNAMIC_RESOURCES_EXCLUDES,
251                           Constants.DYNAMIC_RESOURCES_EXCLUDES_DEFAULT,
252                           Constants.DYNAMIC_RESOURCES_INCLUDES,
253                           Constants.DYNAMIC_RESOURCES_INCLUDES_DEFAULT,
254                           Mechanism.DYNAMIC_RESOURCE,
255                           "/dynamic/*:org.apache.shale.remoting.impl.MethodBindingProcessor");
256         configureMappings(context, mappings, Constants.OTHER_RESOURCES_PARAM,
257                           Constants.OTHER_RESOURCES_EXCLUDES,
258                           Constants.OTHER_RESOURCES_EXCLUDES_DEFAULT,
259                           Constants.OTHER_RESOURCES_INCLUDES,
260                           Constants.OTHER_RESOURCES_INCLUDES_DEFAULT,
261                           Mechanism.OTHER_RESOURCE,
262                           null);
263         configureMappings(context, mappings, Constants.WEBAPP_RESOURCES_PARAM,
264                           Constants.WEBAPP_RESOURCES_EXCLUDES,
265                           Constants.WEBAPP_RESOURCES_EXCLUDES_DEFAULT,
266                           Constants.WEBAPP_RESOURCES_INCLUDES,
267                           Constants.WEBAPP_RESOURCES_INCLUDES_DEFAULT,
268                           Mechanism.WEBAPP_RESOURCE,
269                           "/webapp/*:org.apache.shale.remoting.impl.WebResourceProcessor");
270 
271         // Calculate and set the replacement extension, to be used
272         // if FacesServlet is extension mapped
273         String extension = context.getExternalContext().
274                 getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
275         if (extension == null) {
276             extension = ViewHandler.DEFAULT_SUFFIX;
277         }
278         mappings.setExtension(extension);
279 
280         // Calculate and set the URL patterns that FacesServlet is mapped with
281         // FIXME - hard coded to "*.faces" for now
282         String[] patterns = patterns(context);
283         if (log().isTraceEnabled()) {
284             for (int i = 0; i < patterns.length; i++) {
285                 log().trace("FacesServlet is mapped with URL pattern '" + patterns[i] + "'");
286             }
287         }
288         mappings.setPatterns(patterns);
289 
290         // Calculate the index of the pattern to use by default
291         int patternIndex = 0;
292         String patternIndexString =
293           context.getExternalContext().getInitParameter(Constants.FACES_SERVLET_URL_PARAM);
294         if (patternIndexString != null) {
295             patternIndex = Integer.parseInt(patternIndexString.trim());
296         }
297         if (patternIndex >= patterns.length) {
298             log.warn("FacesServlet pattern index of " + patternIndex
299                      + " does not match any specified pattern");
300         }
301         mappings.setPatternIndex(patternIndex);
302 
303         // Return the configured Mappings instance
304         return mappings;
305 
306     }
307 
308 
309     /***
310      * <p>Load the specified class from the web application class loader
311      * (if possible).</p>
312      *
313      * @param name Fully qualified class name
314      *
315      * @exception ClassNotFoundException if the specified class cannot
316      *  be loaded
317      */
318     private Class loadClass(String name) throws ClassNotFoundException {
319 
320         ClassLoader cl = Thread.currentThread().getContextClassLoader();
321         if (cl == null) {
322             cl = this.getClass().getClassLoader();
323         }
324         return cl.loadClass(name);
325 
326     }
327 
328 
329     /***
330      * <p>Return the <code>Log</code> instance to use, creating one if needed.</p>
331      */
332     private Log log() {
333 
334         if (this.log == null) {
335             log = LogFactory.getLog(MappingsHelper.class);
336         }
337         return log;
338 
339     }
340 
341 
342     /***
343      * <p>Return an array of URL patterns that <code>FacesServlet</code> is
344      * mapped to for this application.</p>
345      *
346      * @param context <code>FacesContext</code> for the current request
347      */
348     private String[] patterns(FacesContext context) {
349 
350         Document document = null;
351         InputStream stream = null;
352 
353         try {
354 
355             // Acquire a URL for /WEB-INF/web.xml (if any)
356             Object ctxt = context.getExternalContext().getContext();
357             Method method =
358                     ctxt.getClass().getMethod("getResource",
359                                               new Class[] { String.class });
360             URL url = (URL) method.invoke(ctxt, new Object[] { "/WEB-INF/web.xml" });
361             if (url == null) {
362                 if (log().isTraceEnabled()) {
363                     log().trace("No /WEB-INF/web.xml resource available, returning empty list");
364                 }
365                 return new String[0];
366             }
367 
368             // Parse this resource into a DOM tree
369             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
370             DocumentBuilder db = dbf.newDocumentBuilder();
371             stream = url.openStream();
372             document = db.parse(stream);
373 
374         } catch (Exception e) {
375 
376             if (log().isErrorEnabled()) {
377                 log().error(bundle.getString("mappings.parseWebXml"), e);
378             }
379             return new String[0];
380 
381         } finally {
382 
383             if (stream != null) {
384                 try { stream.close(); } catch (Exception e) { ; }
385             }
386 
387         }
388 
389         // Identify the servlet name of the JavaServer Faces controller servlet
390         String name =
391                 context.getExternalContext().getInitParameter(Constants.FACES_SERVLET_NAME_PARAM);
392         if (null == name) {
393             NodeList servletNodes = document.getElementsByTagName("servlet");
394             for (int i = 0; i < servletNodes.getLength(); i++) {
395                 Node servletNode = servletNodes.item(i);
396                 String servletName = null;
397                 String servletClass = null;
398                 NodeList kids = servletNode.getChildNodes();
399                 for (int j = 0; j < kids.getLength(); j++) {
400                     Node kid = kids.item(j);
401                     if ("servlet-name".equals(kid.getNodeName())) {
402                         servletName = text(kid);
403                     } else if ("servlet-class".equals(kid.getNodeName())) {
404                         servletClass = text(kid);
405                     }
406                 }
407                 if ("javax.faces.webapp.FacesServlet".equals(servletClass)) {
408                     name = servletName;
409                 }
410             }
411         }
412 
413         // Identify the URL patterns to which this servlet is mapped
414         List list = new ArrayList();
415         NodeList mappingNodes = document.getElementsByTagName("servlet-mapping");
416         for (int i = 0; i < mappingNodes.getLength(); i++) {
417             Node mappingNode = mappingNodes.item(i);
418             String servletName = null;
419             String urlPattern = null;
420             NodeList kids = mappingNode.getChildNodes();
421             for (int j = 0; j < kids.getLength(); j++) {
422                 Node kid = kids.item(j);
423                 if ("servlet-name".equals(kid.getNodeName())) {
424                     servletName = text(kid);
425                 } else if ("url-pattern".equals(kid.getNodeName())) {
426                     urlPattern = text(kid);
427                 }
428             }
429             if (name.equals(servletName)) {
430                 list.add(urlPattern);
431             }
432         }
433 
434         // Return the resulting list
435         return (String[]) list.toArray(new String[list.size()]);
436 
437     }
438 
439 
440     /***
441      * <p>Return the text content inside the specified node.</p>
442      *
443      * @param node Node from which text is to be extracted
444      */
445     private String text(Node node) {
446 
447         NodeList kids = node.getChildNodes();
448         for (int k = 0; k < kids.getLength(); k++) {
449             Node kid = kids.item(k);
450             if ("#text".equals(kid.getNodeName())) {
451                 return kid.getNodeValue().trim();
452             }
453         }
454         return "";
455 
456     }
457 
458 
459 }