View Javadoc

    1   /*
    2    * $Id: ActionComponent.java 497654 2007-01-19 00:21:57Z rgielen $
    3    *
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
    10   * with the License.  You may obtain a copy of the License at
    11   *
    12   *  http://www.apache.org/licenses/LICENSE-2.0
    13   *
    14   * Unless required by applicable law or agreed to in writing,
    15   * software distributed under the License is distributed on an
    16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    17   * KIND, either express or implied.  See the License for the
    18   * specific language governing permissions and limitations
    19   * under the License.
    20   */
    21  package org.apache.struts2.components;
    22
    23  import java.io.IOException;
    24  import java.io.Writer;
    25  import java.util.HashMap;
    26  import java.util.Map;
    27
    28  import javax.servlet.ServletContext;
    29  import javax.servlet.http.HttpServletRequest;
    30  import javax.servlet.http.HttpServletResponse;
    31  import javax.servlet.jsp.PageContext;
    32
    33  import org.apache.commons.logging.Log;
    34  import org.apache.commons.logging.LogFactory;
    35  import org.apache.struts2.views.annotations.StrutsTag;
    36  import org.apache.struts2.views.annotations.StrutsTagAttribute;
    37  import org.apache.struts2.ServletActionContext;
    38  import org.apache.struts2.StrutsException;
    39  import org.apache.struts2.dispatcher.Dispatcher;
    40  import org.apache.struts2.dispatcher.RequestMap;
    41  import org.apache.struts2.views.jsp.TagUtils;
    42
    43  import com.opensymphony.xwork2.ActionContext;
    44  import com.opensymphony.xwork2.ActionProxy;
    45  import com.opensymphony.xwork2.ActionProxyFactory;
    46  import com.opensymphony.xwork2.inject.Inject;
    47  import com.opensymphony.xwork2.util.ValueStack;
    48  import com.opensymphony.xwork2.util.ValueStackFactory;
    49
    50  /***
    51   * <!-- START SNIPPET: javadoc -->
    52   * <p>This tag enables developers to call actions directly from a JSP page by specifying the action name and an optional
    53   * namespace.  The body content of the tag is used to render the results from the Action.  Any result processor defined
    54   * for this action in struts.xml will be ignored, <i>unless</i> the executeResult parameter is specified.</p>
    55   * <!-- END SNIPPET: javadoc -->
    56   *
    57   * <!-- START SNIPPET: params -->
    58   * <ul>
    59   *      <li>id (String) - the id (if specified) to put the action under stack's context.
    60   *      <li>name* (String) - name of the action to be executed (without the extension suffix eg. .action)</li>
    61   *      <li>namespace (String) - default to the namespace where this action tag is invoked</li>
    62   *      <li>executeResult (Boolean) -  default is false. Decides wheather the result of this action is to be executed or not</li>
    63   *      <li>ignoreContextParams (Boolean) - default to false. Decides wheather the request parameters are to be included when the action is invoked</li>
    64   * </ul>
    65   * <!-- END SNIPPET: params -->
    66   *
    67   * <pre>
    68   * <!-- START SNIPPET: javacode -->
    69   * public class ActionTagAction extends ActionSupport {
    70   *
    71   *  public String execute() throws Exception {
    72   *      return "done";
    73   *  }
    74   *
    75   *  public String doDefault() throws Exception {
    76   *      ServletActionContext.getRequest().setAttribute("stringByAction", "This is a String put in by the action's doDefault()");
    77   *      return "done";
    78   *  }
    79   * }
    80   * <!-- END SNIPPET: javacode -->
    81   * </pre>
    82   *
    83   * <pre>
    84   * <!-- START SNIPPET: strutsxml -->
    85   *   <xwork>
    86   *      ....
    87   *     <action name="actionTagAction1" class="tmjee.testing.ActionTagAction">
    88   *         <result name="done">success.jsp</result>
    89   *     </action>
    90   *      <action name="actionTagAction2" class="tmjee.testing.ActionTagAction" method="default">
    91   *         <result name="done">success.jsp</result>
    92   *     </action>
    93   *      ....
    94   *   </xwork>
    95   * <!-- END SNIPPET: strutsxml -->
    96   * </pre>
    97   *
    98   * <pre>
    99   * <!-- START SNIPPET: example -->
    100  *  <div>The following action tag will execute result and include it in this page</div>
    101  *  <br />
    102  *  <s:action name="actionTagAction" executeResult="true" />
    103  *  <br />
    104  *  <div>The following action tag will do the same as above, but invokes method specialMethod in action</div>
    105  *  <br />
    106  *  <s:action name="actionTagAction!specialMethod" executeResult="true" />
    107  *  <br />
    108  *  <div>The following action tag will not execute result, but put a String in request scope
    109  *       under an id "stringByAction" which will be retrieved using property tag</div>
    110  *  <s:action name="actionTagAction!default" executeResult="false" />
    111  *  <s:property value="#attr.stringByAction" />
    112  * <!-- END SNIPPET: example -->
    113  * </pre>
    114  *
    115  */
    116 @StrutsTag(name="action", tldTagClass="org.apache.struts2.views.jsp.ActionTag", description="Execute an action from within a view")
    117 public class ActionComponent extends Component {
    118     private static final Log LOG = LogFactory.getLog(ActionComponent.class);
    119
    120     protected HttpServletResponse res;
    121     protected HttpServletRequest req;
    122
    123     protected ActionProxyFactory actionProxyFactory;
    124     protected ActionProxy proxy;
    125     protected String name;
    126     protected String namespace;
    127     protected boolean executeResult;
    128     protected boolean ignoreContextParams;
    129     protected boolean flush = true;
    130
    131     public ActionComponent(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
    132         super(stack);
    133         this.req = req;
    134         this.res = res;
    135     }
    136
    137     /***
    138      * @param actionProxyFactory the actionProxyFactory to set
    139      */
    140     @Inject
    141     public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
    142         this.actionProxyFactory = actionProxyFactory;
    143     }
    144
    145     public boolean end(Writer writer, String body) {
    146         boolean end = super.end(writer, "", false);
    147         try {
    148             if (flush) {
    149                 try {
    150                     writer.flush();
    151                 } catch (IOException e) {
    152                     LOG.warn("error while trying to flush writer ", e);
    153                 }
    154             }
    155             executeAction();
    156
    157             if ((getId() != null) && (proxy != null)) {
    158                 getStack().setValue("#attr['" + getId() + "']",
    159                         proxy.getAction());
    160             }
    161         } finally {
    162             popComponentStack();
    163         }
    164         return end;
    165     }
    166
    167     private Map createExtraContext() {
    168         Map parentParams = null;
    169
    170         if (!ignoreContextParams) {
    171             parentParams = new ActionContext(getStack().getContext()).getParameters();
    172         }
    173
    174         Map newParams = (parentParams != null) ? new HashMap(parentParams) : new HashMap();
    175
    176         if (parameters != null) {
    177             newParams.putAll(parameters);
    178         }
    179
    180         ActionContext ctx = new ActionContext(stack.getContext());
    181         ServletContext servletContext = (ServletContext) ctx.get(ServletActionContext.SERVLET_CONTEXT);
    182         PageContext pageContext = (PageContext) ctx.get(ServletActionContext.PAGE_CONTEXT);
    183         Map session = ctx.getSession();
    184         Map application = ctx.getApplication();
    185
    186         Dispatcher du = Dispatcher.getInstance();
    187         Map extraContext = du.createContextMap(new RequestMap(req),
    188                 newParams,
    189                 session,
    190                 application,
    191                 req,
    192                 res,
    193                 servletContext);
    194
    195         ValueStack newStack = ValueStackFactory.getFactory().createValueStack(stack);
    196         extraContext.put(ActionContext.VALUE_STACK, newStack);
    197
    198         // add page context, such that ServletDispatcherResult will do an include
    199         extraContext.put(ServletActionContext.PAGE_CONTEXT, pageContext);
    200
    201         return extraContext;
    202     }
    203
    204     public ActionProxy getProxy() {
    205         return proxy;
    206     }
    207
    208     /***
    209      * Execute the requested action.  If no namespace is provided, we'll
    210      * attempt to derive a namespace using buildNamespace().  The ActionProxy
    211      * and the namespace will be saved into the instance variables proxy and
    212      * namespace respectively.
    213      *
    214      * @see org.apache.struts2.views.jsp.TagUtils#buildNamespace
    215      */
    216     private void executeAction() {
    217         String actualName = findString(name, "name", "Action name is required. Example: updatePerson");
    218
    219         if (actualName == null) {
    220             throw new StrutsException("Unable to find value for name " + name);
    221         }
    222
    223         // handle "name!method" convention.
    224         final String actionName;
    225         final String methodName;
    226
    227         int exclamation = actualName.lastIndexOf("!");
    228         if (exclamation != -1) {
    229             actionName = actualName.substring(0, exclamation);
    230             methodName = actualName.substring(exclamation + 1);
    231         } else {
    232             actionName = actualName;
    233             methodName = null;
    234         }
    235
    236         String namespace;
    237
    238         if (this.namespace == null) {
    239             namespace = TagUtils.buildNamespace(actionMapper, getStack(), req);
    240         } else {
    241             namespace = findString(this.namespace);
    242         }
    243
    244         // get the old value stack from the request
    245         ValueStack stack = getStack();
    246         // execute at this point, after params have been set
    247         try {
    248
    249             proxy = actionProxyFactory.createActionProxy(namespace, actionName, createExtraContext(), executeResult, true);
    250             if (null != methodName) {
    251                 proxy.setMethod(methodName);
    252             }
    253             // set the new stack into the request for the taglib to use
    254             req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
    255             proxy.execute();
    256
    257         } catch (Exception e) {
    258             String message = "Could not execute action: " + namespace + "/" + actualName;
    259             LOG.error(message, e);
    260         } finally {
    261             // set the old stack back on the request
    262             req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
    263         }
    264
    265         if ((getId() != null) && (proxy != null)) {
    266             final Map context = stack.getContext();
    267             context.put(getId(), proxy.getAction());
    268         }
    269     }
    270
    271     @StrutsTagAttribute(required=true,description="Name of the action to be executed (without the extension suffix eg. .action)")
    272     public void setName(String name) {
    273         this.name = name;
    274     }
    275
    276     @StrutsTagAttribute(description="Namespace for action to call", defaultValue="namespace from where tag is used")
    277     public void setNamespace(String namespace) {
    278         this.namespace = namespace;
    279     }
    280
    281     @StrutsTagAttribute(description="Whether the result of this action (probably a view) should be executed/rendered", type="Boolean", defaultValue="false")
    282     public void setExecuteResult(boolean executeResult) {
    283         this.executeResult = executeResult;
    284     }
    285
    286     @StrutsTagAttribute(description="Whether the request parameters are to be included when the action is invoked", type="Boolean", defaultValue="false")
    287     public void setIgnoreContextParams(boolean ignoreContextParams) {
    288         this.ignoreContextParams = ignoreContextParams;
    289     }
    290
    291     @StrutsTagAttribute(description="Whether the writer should be flush upon end of action component tag, default to true", type="Boolean", defaultValue="true")
    292     public void setFlush(boolean flush) {
    293         this.flush = flush;
    294     }
    295 }