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