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.FacesException;
22  import javax.faces.context.FacesContext;
23  import javax.servlet.ServletContext;
24  import javax.servlet.http.HttpServletResponse;
25  import org.apache.commons.chain.Catalog;
26  import org.apache.commons.chain.CatalogFactory;
27  import org.apache.commons.chain.Command;
28  import org.apache.commons.chain.Context;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.shale.remoting.Processor;
32  
33  /***
34   * <p>Implementation of {@link Processor} which maps a resource identifier
35   * to the name of a <a href="http://jakarta.apache.org/commons/chain">Commons
36   * Chain</a> command or chain, in an appropriate catalog.  The command or chain
37   * that is executed is passed an appropriate <code>Context</code> object, and
38   * it will also have access to the current JavaServer Faces state by calling
39   * <code>FacesContext.getCurrentInstance()</code>.</p>
40   */
41  public class ChainProcessor implements Processor {
42  
43  
44      // ------------------------------------------------------------ Constructors
45  
46  
47  
48      // ------------------------------------------------------ Instance Variables
49  
50  
51      /***
52       * <p>The <code>Log</code> instance for this class.</p>
53       */
54      private transient Log log = null;
55  
56  
57      // ------------------------------------------------------- Processor Methods
58  
59  
60      /***
61       * <p>Map the specified resource identifier to an appropriate Commons
62       * Chain command or chain, in an appropriate catalog.  Construct an
63       * appropriate <code>Context</code> object, and execute the specified
64       * command or chain, to which we delegate responsibility for creating
65       * the response for the current request.  Call
66       * <code>FacesContext.responseComplete()</code> to tell JavaServer Faces
67       * that the entire response has already been created.</p>
68       *
69       * @param context <code>FacesContext</code> for the current request
70       * @param resourceId Resource identifier used to select the appropriate response
71       *  (this will generally be a context relative path starting with "/")
72       *
73       * @exception IOException if an input/output error occurs
74       * @exception NullPointerException if <code>viewId</code> is <code>null</code>
75       */
76      public void process(FacesContext context, String resourceId) throws IOException {
77  
78          if (log().isDebugEnabled()) {
79              log().debug("Translated resource id '" + resourceId + "' to catalog '"
80                          + mapCatalog(context, resourceId) + "' and command '"
81                          + mapCommand(context, resourceId) + "'");
82          }
83  
84          // Identify the Commons Chain catalog we will be using
85          String catalogName = mapCatalog(context, resourceId);
86          Catalog catalog = CatalogFactory.getInstance().getCatalog(catalogName);
87          if (catalog == null) {
88              if (log().isErrorEnabled()) {
89                  log().error("Cannot find catalog '" + catalogName + "' for resource '"
90                              + resourceId + "'");
91              }
92              sendNotFound(context, resourceId);
93              context.responseComplete();
94              return;
95          }
96  
97          // Identify the Commons Chain chain or command we will be executing
98          String commandName = mapCommand(context, resourceId);
99          Command command = catalog.getCommand(commandName);
100         if (command == null) {
101             if (log().isErrorEnabled()) {
102                 log().error("Cannot find command '" + commandName + "' in catalog '"
103                             + catalogName + "' for resource '" + resourceId + "'");
104             }
105             sendNotFound(context, resourceId);
106             context.responseComplete();
107             return;
108         }
109 
110         // Create a new context and pass it to the specified command
111         try {
112             command.execute(createContext(context, resourceId));
113         } catch (Exception e) {
114             if (log().isErrorEnabled()) {
115                 log().error("Exception executing command '" + commandName
116                             + "' from catalog '" + catalogName + "' for resource '"
117                             + resourceId + "'", e);
118             }
119             sendServerError(context, resourceId, e);
120         }
121 
122         // Tell JavaServer Faces that the current response has been completed
123         context.responseComplete();
124 
125     }
126 
127 
128     // ------------------------------------------------------- Protected Methods
129 
130 
131     /***
132      * <p>Create and return an appropriate <code>Context</code> instance to be
133      * passed to the command or chain that is executed.</p>
134      *
135      * <p>The default algorithm constructs and returns an instance of
136      * {@link ChainContext} that wraps the specified <code>FacesContext</code>.</p>
137      *
138      * @param context <code>FacesContext</code> for the current request
139      * @param resourceId Resource identifier to be mapped
140      */
141     protected Context createContext(FacesContext context, String resourceId) {
142 
143         return new ChainContext(context);
144 
145     }
146 
147 
148     /***
149      * <p>Map the specified resource identifier to the name of a Commons Chain
150      * <code>Catalog</code> from which the command or chain instance will be
151      * acquired.</p>
152      *
153      * <p>The default implementation returns <code>remoting</code>
154      * unconditionally.</p>
155      *
156      * @param context <code>FacesContext</code> for the current request
157      * @param resourceId Resource identifier to be mapped
158      */
159     protected String mapCatalog(FacesContext context, String resourceId) {
160 
161         return "remoting";
162 
163     }
164 
165 
166     /***
167      * <p>Map the specified resource identifier to the name of a Commons Chain
168      * <code>Command</code> or <code>Chain</code>, which will be acquired from
169      * a mapped <code>Catalog</code>.</p>
170      *
171      * <p>The default algorithm performs this conversion as follows:</p>
172      * <ul>
173      * <li>Strip any leading slash character.</li>
174      * <li>Convert embedded slash characters to periods.</li>
175      * </ul>
176      *
177      * @param context <code>FacesContext</code> for the current request
178      * @param resourceId Resource identifier to be mapped
179      */
180     protected String mapCommand(FacesContext context, String resourceId) {
181 
182         // Strip any leading slash character
183         if (resourceId.startsWith("/")) {
184             resourceId = resourceId.substring(1);
185         }
186 
187         // Convert nested slash characters into periods
188         resourceId = resourceId.replace('/', '.');
189 
190         // Return the resulting string
191         return resourceId;
192 
193     }
194 
195 
196     /***
197      * <p>Send a "not found" HTTP response, if possible.  Otherwise, throw an
198      * <code>IllegalArgumentException</code> that will ripple out.</p>
199      *
200      * @param context <code>FacesContext</code> for the current request
201      * @param resourceId Resource identifier of the resource that was not found
202      *
203      * @exception IllegalArgumentException if we cannot send an HTTP response
204      * @exception IOException if an input/output error occurs
205      */
206     protected void sendNotFound(FacesContext context, String resourceId) throws IOException {
207 
208         if (servletRequest(context)) {
209             HttpServletResponse response = (HttpServletResponse)
210               context.getExternalContext().getResponse();
211             response.sendError(HttpServletResponse.SC_NOT_FOUND, resourceId);
212         } else {
213             throw new IllegalArgumentException(resourceId);
214         }
215 
216     }
217 
218 
219     /***
220      * <p>Send a "server error" HTTP response, if possible.  Otherwise, throw a
221      * <code>FacesException</code> that will ripple out.</p>
222      *
223      * @param context <code>FacesContext</code> for the current request
224      * @param resourceId Resource identifier of the resource that was not found
225      * @param e Server exception to be reported
226      *
227      * @exception FacesException if we cannot send an HTTP response
228      * @exception IOException if an input/output error occurs
229      */
230     protected void sendServerError(FacesContext context, String resourceId,
231                                    Exception e) throws IOException {
232 
233         if (servletRequest(context)) {
234             HttpServletResponse response = (HttpServletResponse)
235               context.getExternalContext().getResponse();
236             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, resourceId);
237         } else {
238             throw new FacesException(resourceId);
239         }
240 
241     }
242 
243 
244     /***
245      * <p>Return <code>true</code> if we are processing a servlet request (as
246      * opposed to a portlet request).</p>
247      *
248      * @param context <code>FacesContext</code> for the current request
249      */
250     protected boolean servletRequest(FacesContext context) {
251 
252         return context.getExternalContext().getContext() instanceof ServletContext;
253 
254     }
255 
256 
257     // --------------------------------------------------------- Private Methods
258 
259 
260     /***
261      * <p>Return the <code>Log</code> instance to use, creating one if needed.</p>
262      */
263     private Log log() {
264 
265         if (this.log == null) {
266             log = LogFactory.getLog(ChainProcessor.class);
267         }
268         return log;
269 
270     }
271 
272 
273 }