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.impl;
19  
20  import java.io.IOException;
21  import javax.faces.context.FacesContext;
22  import javax.faces.el.MethodBinding;
23  import javax.servlet.ServletContext;
24  import javax.servlet.http.HttpServletResponse;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.shale.remoting.Constants;
28  import org.apache.shale.remoting.impl.FilteringProcessor;
29  import org.apache.shale.remoting.Processor;
30  
31  /***
32   * <p>Implementation of {@link Processor} which maps a resource identifier
33   * to a method binding expression, then delegates the creation of the current
34   * response to the execution of that method.  The precise details of how a
35   * resource identifier gets mapped are encapsulated in the <code>mapResourceId</code>
36   * method, which may be specialized as desired in a subclass.</p>
37   */
38  public class MethodBindingProcessor extends FilteringProcessor {
39  
40  
41      // ------------------------------------------------------------ Constructors
42  
43  
44  
45      // ------------------------------------------------------ Instance Variables
46  
47  
48      /***
49       * <p><code>Log</code> instance for this class.</p>
50       */
51      private transient Log log = null;
52  
53  
54      // -------------------------------------------------------------- Properties
55  
56  
57      /***
58       * <p>Force our default excludes list to be included.</p>
59       *
60       * @param excludes Application specified excludes list
61       */
62      public void setExcludes(String excludes) {
63  
64          if ((excludes != null) && (excludes.length() > 0)) {
65              super.setExcludes(Constants.DYNAMIC_RESOURCES_EXCLUDES_DEFAULT
66                                + "," + excludes);
67          } else {
68              super.setExcludes(Constants.DYNAMIC_RESOURCES_EXCLUDES_DEFAULT);
69          }
70  
71      }
72  
73  
74      // ------------------------------------------------------- Processor Methods
75  
76  
77      /***
78       * <p>Convert the specified resource identifier into a method binding
79       * expression, and delegate creation of the response to a call to the
80       * identified method.  Call <code>FacesContext.responseComplete()</code>
81       * to tell JavaServer Faces that the entire response has already been
82       * created.</p>
83       *
84       * @param context <code>FacesContext</code> for the current request
85       * @param resourceId Resource identifier used to select the appropriate response
86       *  (this will generally be a context relative path starting with "/")
87       *
88       * @exception IllegalArgumentException if the view identifier is not
89       *  well formed (starting with a '/' character)
90       * @exception IOException if an input/output error occurs
91       * @exception NullPointerException if <code>viewId</code> is <code>null</code>
92       */
93      public void process(FacesContext context, String resourceId) throws IOException {
94  
95          // If someone else has completed the response, we do not have
96          // anything to do
97          if (context.getResponseComplete()) {
98              return;
99          }
100 
101         // Filter based on our includes and excludes patterns
102         if (!accept(resourceId)) {
103             if (log().isTraceEnabled()) {
104                 log().trace("Resource id '" + resourceId
105                             + "' rejected by include/exclude rules");
106             }
107             // Send an HTTP "not found" response to avoid giving the client
108             // any information about a resource that exists and was refused,
109             // versus a resource that does not exist
110             sendNotFound(context, resourceId);
111             context.responseComplete();
112             return;
113         }
114 
115         // Create and execute a method binding based on this resource identifier
116         MethodBinding mb = mapResourceId(context, resourceId);
117         if (log().isDebugEnabled()) {
118             log().debug("Translated resource id '" + resourceId
119                         + "' to method binding expression '"
120                         + mb.getExpressionString() + "'");
121         }
122         mb.invoke(context, new Object[] { });
123 
124         // Tell JavaServer Faces that the current response has been completed
125         context.responseComplete();
126 
127     }
128 
129 
130     // ------------------------------------------------------- Protected Methods
131 
132 
133     /***
134      * <p>Map the specified resource identifier into a corresponding
135      * <code>MethodBinding</code> which identifies the method which will be
136      * called to produce this response.</p>
137      *
138      * <p>The default algorithm performs this conversion as follows:</p>
139      * <ul>
140      * <li>Strip any leading slash character.</li>
141      * <li>Convert embedded slash characters to periods.</li>
142      * <li>Surround the result with "#{" and "}" delimiters.</li>
143      * <li>Ask JavaServer Faces to create a method binding, using this
144      *     expression, for a method that takes no parameters.</li>
145      * </ul>
146      *
147      * @param context <code>FacesContext</code> for the current request
148      * @param resourceId Resource identifier to be mapped
149      */
150     protected MethodBinding mapResourceId(FacesContext context, String resourceId) {
151 
152         // Strip any leading slash character
153         if (resourceId.startsWith("/")) {
154             resourceId = resourceId.substring(1);
155         }
156 
157         // Convert nested slash characters into periods
158         // FIXME - this will have problematic results on managed bean names that have periods
159         resourceId = resourceId.replace('/', '.');
160 
161         // Surround the result with "#{" and "}" delimiters
162         resourceId = "#{" + resourceId + "}";
163 
164         // Return a MethodBinding expression based on this mapping
165         return context.getApplication().createMethodBinding(resourceId,
166                 new Class[] { });
167 
168     }
169 
170 
171 
172     // --------------------------------------------------------- Private Methods
173 
174 
175     /***
176      * <p>Return the <code>Log</code> instance to use, creating one if needed.</p>
177      */
178     private Log log() {
179 
180         if (this.log == null) {
181             log = LogFactory.getLog(MethodBindingProcessor.class);
182         }
183         return log;
184 
185     }
186 
187 
188     /***
189      * <p>Send a "not found" HTTP response, if possible.  Otherwise, throw an
190      * <code>IllegalArgumentException</code> that will ripple out.</p>
191      *
192      * @param context <code>FacesContext</code> for the current request
193      * @param resourceId Resource identifier of the resource that was not found
194      *
195      * @exception IllegalArgumentException if we cannot send an HTTP response
196      * @exception IOException if an input/output error occurs
197      *
198      * @since 1.0.4
199      */
200     private void sendNotFound(FacesContext context, String resourceId) throws IOException {
201 
202         if (servletRequest(context)) {
203             HttpServletResponse response = (HttpServletResponse)
204               context.getExternalContext().getResponse();
205             response.sendError(HttpServletResponse.SC_NOT_FOUND, resourceId);
206         } else {
207             throw new IllegalArgumentException(resourceId);
208         }
209 
210     }
211 
212 
213     /***
214      * <p>Return <code>true</code> if we are processing a servlet request (as
215      * opposed to a portlet request).</p>
216      *
217      * @param context <code>FacesContext</code> for the current request
218      *
219      * @since 1.0.4
220      */
221     private boolean servletRequest(FacesContext context) {
222 
223         return context.getExternalContext().getContext() instanceof ServletContext;
224 
225     }
226 
227 
228 }