001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.pageflow;
020:
021: import org.apache.beehive.netui.pageflow.internal.InternalConstants;
022: import org.apache.beehive.netui.pageflow.internal.PageFlowRequestWrapper;
023: import org.apache.beehive.netui.pageflow.internal.InternalUtils;
024: import org.apache.beehive.netui.pageflow.internal.PageFlowInitialization;
025: import org.apache.beehive.netui.pageflow.handler.Handlers;
026: import org.apache.beehive.netui.pageflow.handler.FlowControllerHandlerContext;
027: import org.apache.beehive.netui.pageflow.handler.ForwardRedirectHandler;
028: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
029: import org.apache.beehive.netui.util.internal.ServletUtils;
030: import org.apache.beehive.netui.util.logging.Logger;
031: import org.apache.struts.config.ModuleConfig;
032: import org.apache.struts.action.RequestProcessor;
033:
034: import javax.servlet.ServletException;
035: import javax.servlet.ServletContext;
036: import javax.servlet.http.HttpServletRequest;
037: import javax.servlet.http.HttpServletResponse;
038: import java.io.Serializable;
039: import java.io.IOException;
040: import java.util.Map;
041:
042: /**
043: * ActionServlet that dynamically registers modules based on naming/location conventions for Struts
044: * configuration files that are generated by the Page Flow compiler. These files are located in
045: * /WEB-INF/classes/_pageflow, and are named struts-config-<i>module-name</i>.xml.
046: * For auto-registration of config files in other locations, the user may specify additional
047: * {@link ModuleConfigLocator} classes in /WEB-INF/beehive-netui-config.xml using the
048: * <code><module-config-locators></code> element.
049: */
050: public class PageFlowActionServlet extends AutoRegisterActionServlet {
051: private static final Logger _log = Logger
052: .getInstance(PageFlowActionServlet.class);
053:
054: private Handlers _handlers;
055:
056: private static final ModuleConfigLocator[] DEFAULT_MODULE_CONFIG_LOCATORS = {
057: new DefaultModuleConfigLocator(),
058: new LegacyModuleConfigLocator() };
059:
060: /**
061: * Get the base list of ModuleConfigLocators, to specify locations for auto-registered Struts modules. By default,
062: * this ActionServlet auto-registers Struts modules whose configuration files are located at
063: * "/WEB-INF/classes/_pageflow/struts-config-<i><module></i>". Overriding this method allows
064: * alternate locations to be specified. When an unrecognized Struts module is requested, each registered
065: * ModuleConfigLocator is queried for a possible path to the configuration file for the module. If the
066: * configuration file is found, the module is auto-registered against the file.
067: */
068: protected ModuleConfigLocator[] getDefaultModuleConfigLocators() {
069: return DEFAULT_MODULE_CONFIG_LOCATORS;
070: }
071:
072: /**
073: * Default ModuleConfigLocator that looks for Struts module configuration files according to the pattern
074: * "/WEB-INF/classes/_pageflow/struts-config-<i><module></i>". An instance of this class
075: * is registered by default.
076: *
077: * @see PageFlowActionServlet#getDefaultModuleConfigLocators
078: */
079: public static class DefaultModuleConfigLocator implements
080: ModuleConfigLocator, Serializable {
081: public String getModuleConfigPath(String moduleName) {
082: InternalStringBuilder moduleConfPath = new InternalStringBuilder(
083: getGenDir());
084: moduleConfPath.append('/').append(
085: PageFlowConstants.PAGEFLOW_MODULE_CONFIG_PREFIX);
086:
087: if (moduleName.length() > 1) {
088: moduleConfPath.append(moduleName.replace('/', '-'));
089: }
090:
091: moduleConfPath
092: .append(PageFlowConstants.PAGEFLOW_MODULE_CONFIG_EXTENSION);
093: return moduleConfPath.toString();
094: }
095:
096: protected String getGenDir() {
097: return PageFlowConstants.PAGEFLOW_MODULE_CONFIG_GEN_DIR;
098: }
099: }
100:
101: /**
102: * ModuleConfigLocator that looks for legacy Struts module configuration files according to the pattern
103: * "/WEB-INF/struts-config-<i><module></i>". An instance of this class is registered by default.
104: *
105: * @see PageFlowActionServlet#getDefaultModuleConfigLocators
106: */
107: protected static class LegacyModuleConfigLocator extends
108: DefaultModuleConfigLocator {
109: protected String getGenDir() {
110: return InternalConstants.WEBINF_DIR;
111: }
112: }
113:
114: public void init() throws ServletException {
115: /* todo: NetUI 1.1 -- need to perform initialization in one place; the ServletContextListener */
116: // Ensure that we get to do our initializations, even if there's no context listener registered in web.xml.
117: ServletContext servletContext = getServletContext();
118:
119: if (!PageFlowInitialization.isInit(servletContext)) {
120: PageFlowInitialization.performInitializations(
121: servletContext, null);
122: }
123:
124: _handlers = Handlers.get(servletContext);
125:
126: super .init();
127: }
128:
129: protected void process(HttpServletRequest request,
130: HttpServletResponse response) throws IOException,
131: ServletException {
132: // If this is a direct request for a shared flow (.jpfs) or a Faces backing bean (.jsfb), return a 404 status.
133: // These are not web-addressable.
134: String servletPath = InternalUtils
135: .getDecodedServletPath(request);
136: if (servletPath
137: .endsWith(InternalConstants.SHARED_FLOW_EXTENSION)
138: || servletPath
139: .endsWith(InternalConstants.FACES_BACKING_EXTENSION)) {
140: if (_log.isDebugEnabled()) {
141: _log.debug("Attempt to hit restricted URI "
142: + servletPath + "; 404 error returned.");
143: }
144:
145: response.sendError(HttpServletResponse.SC_NOT_FOUND);
146: return;
147: }
148:
149: // First, reinitialize the page flow classloader, for reloading when recompile occurs in dev mode.
150: FlowControllerHandlerContext handlerContext = new FlowControllerHandlerContext(
151: request, response, null);
152: _handlers.getReloadableClassHandler().reloadClasses(
153: handlerContext);
154:
155: super .process(request, response);
156: }
157:
158: /**
159: * Get the webapp-relative path to the Struts module configration file for a given module path. By default,
160: * this is "/WEB-INF/classes/_pageflow/struts-config-<i><module></i>", but alternate
161: * locations can be specified by adding {@link ModuleConfigLocator}s.
162: *
163: * @param modulePath the Struts module path.
164: * @return a String that is the path to the Struts configuration file, relative to the web application root.
165: * @see #getDefaultModuleConfigLocators
166: */
167: public String getModuleConfPath(String modulePath) {
168: return super .getModuleConfPath(modulePath);
169: }
170:
171: /**
172: * Struts keeps track of the action servlet URL pattern (e.g., *.do) so it can construct action
173: * URIs. We want to prevent it from noticing *.jpf so it doesn't use that to construct the URIs.
174: *
175: * @exclude
176: */
177: public void addServletMapping(String servletName, String urlPattern) {
178: if (!urlPattern.endsWith(PageFlowConstants.PAGEFLOW_EXTENSION)) {
179: super .addServletMapping(servletName, urlPattern);
180: }
181: }
182:
183: /**
184: * Tell whether the given module can handle the given path. If this is the root module (path=="") and it's a
185: * Page Flow module, then it shouldn't try to handle any path that has a slash in it -- it only handles local
186: * actions.
187: */
188: protected boolean moduleCanHandlePath(ModuleConfig moduleConfig,
189: RequestProcessor rp, String servletPath) {
190: if (moduleConfig.getPrefix().equals("")
191: && servletPath.lastIndexOf('/') > 0
192: && rp instanceof PageFlowRequestProcessor) {
193: return false;
194: }
195:
196: return true;
197: }
198:
199: /**
200: * Last chance to handle an unhandled action URI.
201: * @return <code>true</code> if this method handled it (by forwarding somewhere or writing to the response).
202: */
203: protected boolean processUnhandledAction(
204: HttpServletRequest request, HttpServletResponse response,
205: String uri) throws IOException, ServletException {
206: //
207: // First check to see if we're already in a forwarded fallback request. If so, just bail.
208: //
209: PageFlowRequestWrapper rw = PageFlowRequestWrapper.get(request);
210: if (rw.getOriginalServletPath() != null)
211: return false;
212:
213: SharedFlowController sharedFlowToTry = null;
214: String uriBaseName = ServletUtils.getBaseName(uri);
215: int firstDot = uriBaseName.indexOf('.');
216: int lastDot = uriBaseName.lastIndexOf('.');
217:
218: if (firstDot != -1 && firstDot != lastDot) {
219: String sharedFlowName = uriBaseName.substring(0, firstDot);
220:
221: try {
222: RequestContext rc = new RequestContext(request,
223: response);
224: Map defaultSharedFlows = FlowControllerFactory.get(
225: getServletContext()).getDefaultSharedFlows(rc);
226:
227: if (defaultSharedFlows != null) {
228: sharedFlowToTry = (SharedFlowController) defaultSharedFlows
229: .get(sharedFlowName);
230: uriBaseName = uriBaseName.substring(firstDot + 1);
231: }
232: } catch (ClassNotFoundException e) {
233: ServletUtils.throwServletException(e);
234: } catch (InstantiationException e) {
235: ServletUtils.throwServletException(e);
236: } catch (IllegalAccessException e) {
237: ServletUtils.throwServletException(e);
238: }
239: } else {
240: sharedFlowToTry = FlowControllerFactory.getGlobalApp(
241: request, response, getServletContext());
242: }
243:
244: //
245: // If we couldn't find an appropriate module, try raising the action on the (deprecated) Global.app.
246: //
247:
248: if (sharedFlowToTry != null) {
249: InternalStringBuilder sfActionURI = new InternalStringBuilder(
250: sharedFlowToTry.getModulePath());
251: sfActionURI.append('/');
252: sfActionURI.append(uriBaseName);
253: rw.setOriginalServletPath(uri);
254: ForwardRedirectHandler frh = _handlers
255: .getForwardRedirectHandler();
256: FlowControllerHandlerContext context = new FlowControllerHandlerContext(
257: request, response, null);
258: frh.forward(context, sfActionURI.toString());
259: return true;
260: }
261:
262: return false;
263: }
264: }
|