View Javadoc

    1   /*
    2    * $Id: Component.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.PrintWriter;
    25  import java.io.Writer;
    26  import java.util.HashMap;
    27  import java.util.Iterator;
    28  import java.util.Map;
    29  import java.util.Stack;
    30
    31  import javax.servlet.http.HttpServletRequest;
    32  import javax.servlet.http.HttpServletResponse;
    33
    34  import org.apache.struts2.views.annotations.StrutsTagAttribute;
    35  import org.apache.struts2.StrutsException;
    36  import org.apache.struts2.dispatcher.mapper.ActionMapper;
    37  import org.apache.struts2.dispatcher.mapper.ActionMapping;
    38  import org.apache.struts2.util.FastByteArrayOutputStream;
    39  import org.apache.struts2.views.jsp.TagUtils;
    40  import org.apache.struts2.views.util.ContextUtil;
    41  import org.apache.struts2.views.util.UrlHelper;
    42
    43  import com.opensymphony.xwork2.inject.Inject;
    44  import com.opensymphony.xwork2.util.ValueStack;
    45  import com.opensymphony.xwork2.util.TextParseUtil;
    46
    47  /***
    48   * Base class to extend for UI components.
    49   * <p/>
    50   * This class is a good extension point when building reuseable UI components.
    51   *
    52   */
    53  public class Component {
    54      public static final String COMPONENT_STACK = "__component_stack";
    55
    56      protected ValueStack stack;
    57      protected Map parameters;
    58      protected String id;
    59      protected ActionMapper actionMapper;
    60
    61      /***
    62       * Constructor.
    63       *
    64       * @param stack  OGNL value stack.
    65       */
    66      public Component(ValueStack stack) {
    67          this.stack = stack;
    68          this.parameters = new HashMap();
    69          getComponentStack().push(this);
    70      }
    71
    72      /***
    73       * Get's the name of this component.
    74       * @return the name of this component.
    75       */
    76      private String getComponentName() {
    77          Class c = getClass();
    78          String name = c.getName();
    79          int dot = name.lastIndexOf('.');
    80
    81          return name.substring(dot + 1).toLowerCase();
    82      }
    83
    84      @Inject
    85      public void setActionMapper(ActionMapper mapper) {
    86          this.actionMapper = mapper;
    87      }
    88
    89      /***
    90       * Get's the OGNL value stack assoicated with this component.
    91       * @return the OGNL value stack assoicated with this component.
    92       */
    93      public ValueStack getStack() {
    94          return stack;
    95      }
    96
    97      /***
    98       * Get's the component stack of this component.
    99       * @return the component stack of this component, never <tt>null</tt>.
    100      */
    101     public Stack getComponentStack() {
    102         Stack componentStack = (Stack) stack.getContext().get(COMPONENT_STACK);
    103         if (componentStack == null) {
    104             componentStack = new Stack();
    105             stack.getContext().put(COMPONENT_STACK, componentStack);
    106         }
    107         return componentStack;
    108     }
    109
    110     /***
    111      * Callback for the start tag of this component.
    112      * Should the body be evaluated?
    113      *
    114      * @param writer  the output writer.
    115      * @return true if the body should be evaluated
    116      */
    117     public boolean start(Writer writer) {
    118         return true;
    119     }
    120
    121     /***
    122      * Callback for the end tag of this component.
    123      * Should the body be evaluated again?
    124      * <p/>
    125      * <b>NOTE:</b> will pop component stack.
    126      * @param writer  the output writer.
    127      * @param body    the rendered body.
    128      * @return true if the body should be evaluated again
    129      */
    130     public boolean end(Writer writer, String body) {
    131         return end(writer, body, true);
    132     }
    133
    134     /***
    135      * Callback for the start tag of this component.
    136      * Should the body be evaluated again?
    137      * <p/>
    138      * <b>NOTE:</b> has a parameter to determine to pop the component stack.
    139      * @param writer  the output writer.
    140      * @param body    the rendered body.
    141      * @param popComponentStack  should the component stack be popped?
    142      * @return true if the body should be evaluated again
    143      */
    144     protected boolean end(Writer writer, String body, boolean popComponentStack) {
    145         assert(body != null);
    146
    147         try {
    148             writer.write(body);
    149         } catch (IOException e) {
    150             throw new StrutsException("IOError while writing the body: " + e.getMessage(), e);
    151         }
    152         if (popComponentStack) {
    153             popComponentStack();
    154         }
    155         return false;
    156     }
    157
    158     /***
    159      * Pops the component stack.
    160      */
    161     protected void popComponentStack() {
    162         getComponentStack().pop();
    163     }
    164
    165     /***
    166      * Finds the nearest ancestor of this component stack.
    167      * @param clazz the class to look for, or if assignable from.
    168      * @return  the component if found, <tt>null</tt> if not.
    169      */
    170     protected Component findAncestor(Class clazz) {
    171         Stack componentStack = getComponentStack();
    172         int currPosition = componentStack.search(this);
    173         if (currPosition >= 0) {
    174             int start = componentStack.size() - currPosition - 1;
    175
    176             //for (int i = componentStack.size() - 2; i >= 0; i--) {
    177             for (int i = start; i >=0; i--) {
    178                 Component component = (Component) componentStack.get(i);
    179                 if (clazz.isAssignableFrom(component.getClass()) && component != this) {
    180                     return component;
    181                 }
    182             }
    183         }
    184
    185         return null;
    186     }
    187
    188     /***
    189      * Evaluates the OGNL stack to find a String value.
    190      * @param expr  OGNL expression.
    191      * @return  the String value found.
    192      */
    193     protected String findString(String expr) {
    194         return (String) findValue(expr, String.class);
    195     }
    196
    197     /***
    198      * Evaluates the OGNL stack to find a String value.
    199      * <p/>
    200      * If the given expression is <tt>null</tt/> a error is logged and a <code>RuntimeException</code> is thrown
    201      * constructed with a messaged based on the given field and errorMsg paramter.
    202      *
    203      * @param expr  OGNL expression.
    204      * @param field   field name used when throwing <code>RuntimeException</code>.
    205      * @param errorMsg  error message used when throwing <code>RuntimeException</code>.
    206      * @return  the String value found.
    207      * @throws StrutsException is thrown in case of expression is <tt>null</tt>.
    208      */
    209     protected String findString(String expr, String field, String errorMsg) {
    210         if (expr == null) {
    211             throw fieldError(field, errorMsg, null);
    212         } else {
    213             return findString(expr);
    214         }
    215     }
    216
    217     /***
    218      * Constructs?a <code>RuntimeException</code> based on the given information.
    219      * <p/>
    220      * A message is constructed and logged at ERROR level before being returned
    221      * as a <code>RuntimeException</code>.
    222      * @param field   field name used when throwing <code>RuntimeException</code>.
    223      * @param errorMsg  error message used when throwing <code>RuntimeException</code>.
    224      * @param e  the caused exception, can be <tt>null</tt>.
    225      * @return  the constructed <code>StrutsException</code>.
    226      */
    227     protected StrutsException fieldError(String field, String errorMsg, Exception e) {
    228         String msg = "tag '" + getComponentName() + "', field '" + field + ( id != null ?"', id '" + id:"") +
    229                 ( parameters != null && parameters.containsKey("name")?"', name '" + parameters.get("name"):"") +
    230                 "': " + errorMsg;
    231         throw new StrutsException(msg, e);
    232     }
    233
    234     /***
    235      * Finds a value from the OGNL stack based on the given expression.
    236      * Will always evaluate <code>expr</code> against stack except when <code>expr</code>
    237      * is null. If altsyntax (%{...}) is applied, simply strip it off.
    238      *
    239      * @param expr  the expression. Returns <tt>null</tt> if expr is null.
    240      * @return the value, <tt>null</tt> if not found.
    241      */
    242     protected Object findValue(String expr) {
    243         if (expr == null) {
    244             return null;
    245         }
    246
    247         if (altSyntax()) {
    248             // does the expression start with %{ and end with }? if so, just cut it off!
    249             if (expr.startsWith("%{") && expr.endsWith("}")) {
    250                 expr = expr.substring(2, expr.length() - 1);
    251             }
    252         }
    253
    254         return getStack().findValue(expr);
    255     }
    256
    257     /***
    258      * Is the altSyntax enabled? [TRUE]
    259      * <p/>
    260      * See <code>struts.properties</code> where the altSyntax flag is defined.
    261      */
    262     public boolean altSyntax() {
    263         return ContextUtil.isUseAltSyntax(stack.getContext());
    264     }
    265
    266     /***
    267      * Evaluates the OGNL stack to find an Object value.
    268      * <p/>
    269      * Function just like <code>findValue(String)</code> except that if the
    270      * given expression is <tt>null</tt/> a error is logged and
    271      * a <code>RuntimeException</code> is thrown constructed with a
    272      * messaged based on the given field and errorMsg paramter.
    273      *
    274      * @param expr  OGNL expression.
    275      * @param field   field name used when throwing <code>RuntimeException</code>.
    276      * @param errorMsg  error message used when throwing <code>RuntimeException</code>.
    277      * @return  the Object found, is never <tt>null</tt>.
    278      * @throws StrutsException is thrown in case of not found in the OGNL stack, or expression is <tt>null</tt>.
    279      */
    280     protected Object findValue(String expr, String field, String errorMsg) {
    281         if (expr == null) {
    282             throw fieldError(field, errorMsg, null);
    283         } else {
    284             Object value = null;
    285             Exception problem = null;
    286             try {
    287                 value = findValue(expr);
    288             } catch (Exception e) {
    289                 problem = e;
    290             }
    291
    292             if (value == null) {
    293                 throw fieldError(field, errorMsg, problem);
    294             }
    295
    296             return value;
    297         }
    298     }
    299
    300     /***
    301      * Evaluates the OGNL stack to find an Object of the given type. Will evaluate
    302      * <code>expr</code> the portion wrapped with altSyntax (%{...})
    303      * against stack when altSyntax is on, else the whole <code>expr</code>
    304      * is evaluated against the stack.
    305      * <p/>
    306      * This method only supports the altSyntax. So this should be set to true.
    307      * @param expr  OGNL expression.
    308      * @param toType  the type expected to find.
    309      * @return  the Object found, or <tt>null</tt> if not found.
    310      */
    311     protected Object findValue(String expr, Class toType) {
    312         if (altSyntax() && toType == String.class) {
    313             return TextParseUtil.translateVariables('%', expr, stack);
    314         } else {
    315             if (altSyntax()) {
    316                 // does the expression start with %{ and end with }? if so, just cut it off!
    317                 if (expr.startsWith("%{") && expr.endsWith("}")) {
    318                     expr = expr.substring(2, expr.length() - 1);
    319                 }
    320             }
    321
    322             return getStack().findValue(expr, toType);
    323         }
    324     }
    325
    326     /***
    327      * Renders an action URL by consulting the {@link org.apache.struts2.dispatcher.mapper.ActionMapper}.
    328      * @param action      the action
    329      * @param namespace   the namespace
    330      * @param method      the method
    331      * @param req         HTTP request
    332      * @param res         HTTP response
    333      * @param parameters  parameters
    334      * @param scheme      http or https
    335      * @param includeContext  should the context path be included or not
    336      * @param encodeResult    should the url be encoded
    337      * @return the action url.
    338      */
    339     protected String determineActionURL(String action, String namespace, String method,
    340                                         HttpServletRequest req, HttpServletResponse res, Map parameters, String scheme,
    341                                         boolean includeContext, boolean encodeResult) {
    342         String finalAction = findString(action);
    343         String finalNamespace = determineNamespace(namespace, getStack(), req);
    344         ActionMapping mapping = new ActionMapping(finalAction, finalNamespace, method, parameters);
    345         String uri = actionMapper.getUriFromActionMapping(mapping);
    346         return UrlHelper.buildUrl(uri, req, res, parameters, scheme, includeContext, encodeResult);
    347     }
    348
    349     /***
    350      * Determines the namespace of the current page being renderdd. Useful for Form, URL, and href generations.
    351      * @param namespace  the namespace
    352      * @param stack      OGNL value stack
    353      * @param req        HTTP request
    354      * @return  the namepsace of the current page being rendered, is never <tt>null</tt>.
    355      */
    356     protected String determineNamespace(String namespace, ValueStack stack, HttpServletRequest req) {
    357         String result;
    358
    359         if (namespace == null) {
    360             result = TagUtils.buildNamespace(actionMapper, stack, req);
    361         } else {
    362             result = findString(namespace);
    363         }
    364
    365         if (result == null) {
    366             result = "";
    367         }
    368
    369         return result;
    370     }
    371
    372     /***
    373      * Pushes this component's parameter Map as well as the component itself on to the stack
    374      * and then copies the supplied parameters over. Because the component's parameter Map is
    375      * pushed before the component itself, any key-value pair that can't be assigned to componet
    376      * will be set in the parameters Map.
    377      *
    378      * @param params  the parameters to copy.
    379      */
    380     public void copyParams(Map params) {
    381         stack.push(parameters);
    382         stack.push(this);
    383         try {
    384             for (Iterator iterator = params.entrySet().iterator(); iterator.hasNext();) {
    385                 Map.Entry entry = (Map.Entry) iterator.next();
    386                 String key = (String) entry.getKey();
    387                 stack.setValue(key, entry.getValue());
    388             }
    389         } finally {
    390             stack.pop();
    391             stack.pop();
    392         }
    393     }
    394
    395     /***
    396      * Constructs a string representation of the given exception.
    397      * @param t  the exception
    398      * @return the exception as a string.
    399      */
    400     protected String toString(Throwable t) {
    401         FastByteArrayOutputStream bout = new FastByteArrayOutputStream();
    402         PrintWriter wrt = new PrintWriter(bout);
    403         t.printStackTrace(wrt);
    404         wrt.close();
    405
    406         return bout.toString();
    407     }
    408
    409     /***
    410      * Get's the parameters.
    411      * @return the parameters. Is never <tt>null</tt>.
    412      */
    413     public Map getParameters() {
    414         return parameters;
    415     }
    416
    417     /***
    418      * Add's all the given parameters to this componenets own parameters.
    419      * @param params the parameters to add.
    420      */
    421     public void addAllParameters(Map params) {
    422         parameters.putAll(params);
    423     }
    424
    425     /***
    426      * Add's the given key and value to this components own parameter.
    427      * <p/>
    428      * If the provided key is <tt>null</tt> nothing happends.
    429      * If the provided value is <tt>null</tt> any existing parameter with
    430      * the given key name is removed.
    431      * @param key  the key of the new parameter to add.
    432      * @param value the value assoicated with the key.
    433      */
    434     public void addParameter(String key, Object value) {
    435         if (key != null) {
    436             Map params = getParameters();
    437
    438             if (value == null) {
    439                 params.remove(key);
    440             } else {
    441                 params.put(key, value);
    442             }
    443         }
    444     }
    445
    446     /***
    447      * Get's the id for referencing element.
    448      * @return the id for referencing element.
    449      */
    450     public String getId() {
    451         return id;
    452     }
    453
    454     @StrutsTagAttribute(description="id for referencing element. For UI and form tags it will be used as HTML id attribute")
    455     public void setId(String id) {
    456         if (id != null) {
    457             this.id = findString(id);
    458         }
    459     }
    460
    461     /***
    462      * Overwrite to set if body shold be used.
    463      * @return always false for this component.
    464      */
    465     public boolean usesBody() {
    466         return false;
    467     }
    468
    469 }