1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
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
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 }