// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.madvoc; import jodd.madvoc.component.MadvocController; import jodd.madvoc.injector.Target; import jodd.exception.ExceptionUtil; import jodd.madvoc.meta.Out; import jodd.madvoc.result.Result; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; /** * Encapsulates single action invocation and acts as an action proxy. * It invokes all assigned action interceptors during action invocation and * specifies the result after action method invocation. */ public class ActionRequest { protected final MadvocController madvocController; protected final ActionConfig actionConfig; protected final String actionPath; protected HttpServletRequest servletRequest; protected HttpServletResponse servletResponse; protected Result result; protected final Target[] targets; protected final ActionWrapper[] executionArray; protected int executionIndex; protected Object action; protected Object actionResult; protected String nextActionPath; protected ActionRequest previousActionRequest; // ---------------------------------------------------------------- accessors /** * Returns servlet request. */ public HttpServletRequest getHttpServletRequest() { return servletRequest; } /** * Specifies new servlet request, in case of wrapping it. */ public void setHttpServletRequest(HttpServletRequest request) { this.servletRequest = request; } /** * Returns servlet response. */ public HttpServletResponse getHttpServletResponse() { return servletResponse; } /** * Specifies new servlet response, in case of wrapping it. */ public void setHttpServletResponse(HttpServletResponse response) { this.servletResponse = response; } /** * Returns {@link ActionConfig action configuration}. */ public ActionConfig getActionConfig() { return actionConfig; } /** * Returns action object. */ public Object getAction() { return action; } /** * Returns action path. */ public String getActionPath() { return actionPath; } /** * Returns next request string for action chaining. */ public String getNextActionPath() { return nextActionPath; } /** * Specifies the next action path, that will be chained to current action request. */ public void setNextActionPath(String nextActionPath) { this.nextActionPath = nextActionPath; } /** * Returns previous action request in chain, if there was one. */ public ActionRequest getPreviousActionRequest() { return previousActionRequest; } /** * Sets previous action request in chain. */ public void setPreviousActionRequest(ActionRequest previousActionRequest) { this.previousActionRequest = previousActionRequest; } /** * Returns result object if exist in action, otherwise returns <code>null</code>. */ public Result getResult() { return result; } /** * Returns all injection targets. */ public Target[] getTargets() { return targets; } /** * Returns action result object. */ public Object getActionResult() { return actionResult; } /** * Sets action result object. */ public void setActionResult(Object actionResult) { this.actionResult = actionResult; } // ---------------------------------------------------------------- ctor /** * Creates new action request and initializes it. */ public ActionRequest( MadvocController madvocController, String actionPath, ActionConfig actionConfig, Object action, HttpServletRequest servletRequest, HttpServletResponse servletResponse) { this.madvocController = madvocController; this.actionPath = actionPath; this.actionConfig = actionConfig; this.servletRequest = servletRequest; this.servletResponse = servletResponse; this.action = action; this.result = findResult(); this.targets = makeTargets(); this.executionIndex = 0; this.executionArray = createExecutionArray(); } /** * Creates execution array that will invoke all filters, actions and results * in correct order. */ protected ActionWrapper[] createExecutionArray() { int totalInterceptors = (this.actionConfig.interceptors != null ? this.actionConfig.interceptors.length : 0); int totalFilters = (this.actionConfig.filters != null ? this.actionConfig.filters.length : 0); ActionWrapper[] executionArray = new ActionWrapper[totalFilters + 1 + totalInterceptors + 1]; // filters int index = 0; if (totalFilters > 0) { System.arraycopy(actionConfig.filters, 0, executionArray, index, totalFilters); index += totalFilters; } // result is executed AFTER the action AND interceptors executionArray[index++] = new BaseActionWrapper() { public Object invoke(ActionRequest actionRequest) throws Exception { Object actionResult = actionRequest.invoke(); ActionRequest.this.madvocController.render(ActionRequest.this, actionResult); return actionResult; } }; // interceptors if (totalInterceptors > 0) { System.arraycopy(actionConfig.interceptors, 0, executionArray, index, totalInterceptors); index += totalInterceptors; } // action executionArray[index] = new BaseActionWrapper() { public Object invoke(ActionRequest actionRequest) throws Exception { actionResult = invokeActionMethod(); return actionResult; } }; return executionArray; } /** * Returns result field value if such exist. If field exists * and it's value is <code>null</code> it will be created. */ protected Result findResult() { Field resultField = actionConfig.resultField; if (resultField != null) { try { Result result = (Result) resultField.get(action); if (result == null) { result = (Result) resultField.getType().newInstance(); resultField.set(action, result); } return result; } catch (Exception ignore) { return null; } } return null; } /** * Joins action and parameters into one array of Targets. */ protected Target[] makeTargets() { if (!actionConfig.hasArguments) { return new Target[] {new Target(action)}; } ActionConfig.MethodParam[] methodParams = actionConfig.getMethodParams(); Target[] target = new Target[methodParams.length + 1]; target[0] = new Target(action); for (int i = 0; i < methodParams.length; i++) { ActionConfig.MethodParam mp = methodParams[i]; Class type = mp.getType(); Target t; if (mp.getAnnotationType() == null) { // parameter is NOT annotated t = new Target(createActionMethodArgument(type)); } else if (mp.getAnnotationType() == Out.class) { // parameter is annotated with *only* OUT annotation // we need to create the output AND to save the type t = new Target(createActionMethodArgument(type), type); } else { // parameter is annotated with any IN annotation t = new Target(type) { @Override protected void createValueInstance() { value = createActionMethodArgument(type); } }; } target[i + 1] = t; } return target; } /** * Creates action method arguments. */ @SuppressWarnings({"unchecked", "NullArgumentToVariableArgMethod"}) protected Object createActionMethodArgument(Class type) { try { if (type.getEnclosingClass() == null || Modifier.isStatic(type.getModifiers())) { // regular or static class Constructor ctor = type.getDeclaredConstructor(null); ctor.setAccessible(true); return ctor.newInstance(); } else { // member class Constructor ctor = type.getDeclaredConstructor(type.getDeclaringClass()); ctor.setAccessible(true); return ctor.newInstance(action); } } catch (Exception ex) { throw new MadvocException(ex); } } // ---------------------------------------------------------------- invoke /** * Invokes the action and returns action result value object. * Invokes all interceptors before and after action invocation. */ public Object invoke() throws Exception { return executionArray[executionIndex++].invoke(this); } /** * Invokes action method after starting all interceptors. * After method invocation, all interceptors will finish, in opposite order. */ protected Object invokeActionMethod() throws Exception { Object[] params = extractParametersFromTargets(); try { return actionConfig.actionClassMethod.invoke(action, params); } catch(InvocationTargetException itex) { throw ExceptionUtil.extractTargetException(itex); } } /** * Collects all parameters from target into an array. */ protected Object[] extractParametersFromTargets() { if (targets == null) { return null; } Object[] values = new Object[targets.length - 1]; for (int i = 1; i < targets.length; i++) { values[i - 1] = targets[i].getValue(); } return values; } }