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