Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ChainProcessor |
|
| 2.875;2.875 |
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 | 9 | 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 | 9 | 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 | 3 | 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 | 3 | String catalogName = mapCatalog(context, resourceId); |
86 | 3 | Catalog catalog = CatalogFactory.getInstance().getCatalog(catalogName); |
87 | 3 | if (catalog == null) { |
88 | 1 | if (log().isErrorEnabled()) { |
89 | 1 | log().error("Cannot find catalog '" + catalogName + "' for resource '" |
90 | + resourceId + "'"); | |
91 | } | |
92 | 1 | sendNotFound(context, resourceId); |
93 | 1 | context.responseComplete(); |
94 | 1 | return; |
95 | } | |
96 | ||
97 | // Identify the Commons Chain chain or command we will be executing | |
98 | 2 | String commandName = mapCommand(context, resourceId); |
99 | 2 | Command command = catalog.getCommand(commandName); |
100 | 2 | if (command == null) { |
101 | 1 | if (log().isErrorEnabled()) { |
102 | 1 | log().error("Cannot find command '" + commandName + "' in catalog '" |
103 | + catalogName + "' for resource '" + resourceId + "'"); | |
104 | } | |
105 | 1 | sendNotFound(context, resourceId); |
106 | 1 | context.responseComplete(); |
107 | 1 | return; |
108 | } | |
109 | ||
110 | // Create a new context and pass it to the specified command | |
111 | try { | |
112 | 1 | 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 | 1 | } |
121 | ||
122 | // Tell JavaServer Faces that the current response has been completed | |
123 | 1 | context.responseComplete(); |
124 | ||
125 | 1 | } |
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 | 2 | 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 | 5 | 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 | 4 | if (resourceId.startsWith("/")) { |
184 | 4 | resourceId = resourceId.substring(1); |
185 | } | |
186 | ||
187 | // Convert nested slash characters into periods | |
188 | 4 | resourceId = resourceId.replace('/', '.'); |
189 | ||
190 | // Return the resulting string | |
191 | 4 | 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 | 3 | if (servletRequest(context)) { |
209 | 3 | HttpServletResponse response = (HttpServletResponse) |
210 | context.getExternalContext().getResponse(); | |
211 | 3 | response.sendError(HttpServletResponse.SC_NOT_FOUND, resourceId); |
212 | 3 | } else { |
213 | throw new IllegalArgumentException(resourceId); | |
214 | } | |
215 | ||
216 | 3 | } |
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 | 1 | if (servletRequest(context)) { |
234 | 1 | HttpServletResponse response = (HttpServletResponse) |
235 | context.getExternalContext().getResponse(); | |
236 | 1 | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, resourceId); |
237 | 1 | } else { |
238 | throw new FacesException(resourceId); | |
239 | } | |
240 | ||
241 | 1 | } |
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 | 4 | 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 | 7 | if (this.log == null) { |
266 | 4 | log = LogFactory.getLog(ChainProcessor.class); |
267 | } | |
268 | 7 | return log; |
269 | ||
270 | } | |
271 | ||
272 | ||
273 | } |