View Javadoc

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