001: /*
002: * $Id: Component.java 576454 2007-09-17 14:35:17Z jholmes $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.components;
022:
023: import java.io.IOException;
024: import java.io.PrintWriter;
025: import java.io.Writer;
026: import java.util.Iterator;
027: import java.util.LinkedHashMap;
028: import java.util.Map;
029: import java.util.Stack;
030:
031: import javax.servlet.http.HttpServletRequest;
032: import javax.servlet.http.HttpServletResponse;
033:
034: import org.apache.struts2.views.annotations.StrutsTagAttribute;
035: import org.apache.struts2.StrutsException;
036: import org.apache.struts2.dispatcher.mapper.ActionMapper;
037: import org.apache.struts2.dispatcher.mapper.ActionMapping;
038: import org.apache.struts2.util.FastByteArrayOutputStream;
039: import org.apache.struts2.views.jsp.TagUtils;
040: import org.apache.struts2.views.util.ContextUtil;
041: import org.apache.struts2.views.util.UrlHelper;
042:
043: import com.opensymphony.xwork2.inject.Inject;
044: import com.opensymphony.xwork2.util.ValueStack;
045: import com.opensymphony.xwork2.util.TextParseUtil;
046:
047: /**
048: * Base class to extend for UI components.
049: * <p/>
050: * This class is a good extension point when building reuseable UI components.
051: *
052: */
053: public class Component {
054: public static final String COMPONENT_STACK = "__component_stack";
055:
056: protected ValueStack stack;
057: protected Map parameters;
058: protected String id;
059: protected ActionMapper actionMapper;
060:
061: /**
062: * Constructor.
063: *
064: * @param stack OGNL value stack.
065: */
066: public Component(ValueStack stack) {
067: this .stack = stack;
068: this .parameters = new LinkedHashMap();
069: getComponentStack().push(this );
070: }
071:
072: /**
073: * Gets the name of this component.
074: * @return the name of this component.
075: */
076: private String getComponentName() {
077: Class c = getClass();
078: String name = c.getName();
079: int dot = name.lastIndexOf('.');
080:
081: return name.substring(dot + 1).toLowerCase();
082: }
083:
084: @Inject
085: public void setActionMapper(ActionMapper mapper) {
086: this .actionMapper = mapper;
087: }
088:
089: /**
090: * Gets the OGNL value stack assoicated with this component.
091: * @return the OGNL value stack assoicated with this component.
092: */
093: public ValueStack getStack() {
094: return stack;
095: }
096:
097: /**
098: * Gets the component stack of this component.
099: * @return the component stack of this component, never <tt>null</tt>.
100: */
101: public Stack getComponentStack() {
102: Stack componentStack = (Stack) stack.getContext().get(
103: COMPONENT_STACK);
104: if (componentStack == null) {
105: componentStack = new Stack();
106: stack.getContext().put(COMPONENT_STACK, componentStack);
107: }
108: return componentStack;
109: }
110:
111: /**
112: * Callback for the start tag of this component.
113: * Should the body be evaluated?
114: *
115: * @param writer the output writer.
116: * @return true if the body should be evaluated
117: */
118: public boolean start(Writer writer) {
119: return true;
120: }
121:
122: /**
123: * Callback for the end tag of this component.
124: * Should the body be evaluated again?
125: * <p/>
126: * <b>NOTE:</b> will pop component stack.
127: * @param writer the output writer.
128: * @param body the rendered body.
129: * @return true if the body should be evaluated again
130: */
131: public boolean end(Writer writer, String body) {
132: return end(writer, body, true);
133: }
134:
135: /**
136: * Callback for the start tag of this component.
137: * Should the body be evaluated again?
138: * <p/>
139: * <b>NOTE:</b> has a parameter to determine to pop the component stack.
140: * @param writer the output writer.
141: * @param body the rendered body.
142: * @param popComponentStack should the component stack be popped?
143: * @return true if the body should be evaluated again
144: */
145: protected boolean end(Writer writer, String body,
146: boolean popComponentStack) {
147: assert (body != null);
148:
149: try {
150: writer.write(body);
151: } catch (IOException e) {
152: throw new StrutsException(
153: "IOError while writing the body: " + e.getMessage(),
154: e);
155: }
156: if (popComponentStack) {
157: popComponentStack();
158: }
159: return false;
160: }
161:
162: /**
163: * Pops the component stack.
164: */
165: protected void popComponentStack() {
166: getComponentStack().pop();
167: }
168:
169: /**
170: * Finds the nearest ancestor of this component stack.
171: * @param clazz the class to look for, or if assignable from.
172: * @return the component if found, <tt>null</tt> if not.
173: */
174: protected Component findAncestor(Class clazz) {
175: Stack componentStack = getComponentStack();
176: int currPosition = componentStack.search(this );
177: if (currPosition >= 0) {
178: int start = componentStack.size() - currPosition - 1;
179:
180: //for (int i = componentStack.size() - 2; i >= 0; i--) {
181: for (int i = start; i >= 0; i--) {
182: Component component = (Component) componentStack.get(i);
183: if (clazz.isAssignableFrom(component.getClass())
184: && component != this ) {
185: return component;
186: }
187: }
188: }
189:
190: return null;
191: }
192:
193: /**
194: * Evaluates the OGNL stack to find a String value.
195: * @param expr OGNL expression.
196: * @return the String value found.
197: */
198: protected String findString(String expr) {
199: return (String) findValue(expr, String.class);
200: }
201:
202: /**
203: * Evaluates the OGNL stack to find a String value.
204: * <p/>
205: * If the given expression is <tt>null</tt/> a error is logged and a <code>RuntimeException</code> is thrown
206: * constructed with a messaged based on the given field and errorMsg paramter.
207: *
208: * @param expr OGNL expression.
209: * @param field field name used when throwing <code>RuntimeException</code>.
210: * @param errorMsg error message used when throwing <code>RuntimeException</code>.
211: * @return the String value found.
212: * @throws StrutsException is thrown in case of expression is <tt>null</tt>.
213: */
214: protected String findString(String expr, String field,
215: String errorMsg) {
216: if (expr == null) {
217: throw fieldError(field, errorMsg, null);
218: } else {
219: return findString(expr);
220: }
221: }
222:
223: /**
224: * Constructs a <code>RuntimeException</code> based on the given information.
225: * <p/>
226: * A message is constructed and logged at ERROR level before being returned
227: * as a <code>RuntimeException</code>.
228: * @param field field name used when throwing <code>RuntimeException</code>.
229: * @param errorMsg error message used when throwing <code>RuntimeException</code>.
230: * @param e the caused exception, can be <tt>null</tt>.
231: * @return the constructed <code>StrutsException</code>.
232: */
233: protected StrutsException fieldError(String field, String errorMsg,
234: Exception e) {
235: String msg = "tag '"
236: + getComponentName()
237: + "', field '"
238: + field
239: + (id != null ? "', id '" + id : "")
240: + (parameters != null && parameters.containsKey("name") ? "', name '"
241: + parameters.get("name")
242: : "") + "': " + errorMsg;
243: throw new StrutsException(msg, e);
244: }
245:
246: /**
247: * Finds a value from the OGNL stack based on the given expression.
248: * Will always evaluate <code>expr</code> against stack except when <code>expr</code>
249: * is null. If altsyntax (%{...}) is applied, simply strip it off.
250: *
251: * @param expr the expression. Returns <tt>null</tt> if expr is null.
252: * @return the value, <tt>null</tt> if not found.
253: */
254: protected Object findValue(String expr) {
255: if (expr == null) {
256: return null;
257: }
258:
259: if (altSyntax()) {
260: // does the expression start with %{ and end with }? if so, just cut it off!
261: if (expr.startsWith("%{") && expr.endsWith("}")) {
262: expr = expr.substring(2, expr.length() - 1);
263: }
264: }
265:
266: return getStack().findValue(expr);
267: }
268:
269: /**
270: * Is the altSyntax enabled? [TRUE]
271: * <p/>
272: * See <code>struts.properties</code> where the altSyntax flag is defined.
273: */
274: public boolean altSyntax() {
275: return ContextUtil.isUseAltSyntax(stack.getContext());
276: }
277:
278: /**
279: * Evaluates the OGNL stack to find an Object value.
280: * <p/>
281: * Function just like <code>findValue(String)</code> except that if the
282: * given expression is <tt>null</tt/> a error is logged and
283: * a <code>RuntimeException</code> is thrown constructed with a
284: * messaged based on the given field and errorMsg paramter.
285: *
286: * @param expr OGNL expression.
287: * @param field field name used when throwing <code>RuntimeException</code>.
288: * @param errorMsg error message used when throwing <code>RuntimeException</code>.
289: * @return the Object found, is never <tt>null</tt>.
290: * @throws StrutsException is thrown in case of not found in the OGNL stack, or expression is <tt>null</tt>.
291: */
292: protected Object findValue(String expr, String field,
293: String errorMsg) {
294: if (expr == null) {
295: throw fieldError(field, errorMsg, null);
296: } else {
297: Object value = null;
298: Exception problem = null;
299: try {
300: value = findValue(expr);
301: } catch (Exception e) {
302: problem = e;
303: }
304:
305: if (value == null) {
306: throw fieldError(field, errorMsg, problem);
307: }
308:
309: return value;
310: }
311: }
312:
313: /**
314: * Evaluates the OGNL stack to find an Object of the given type. Will evaluate
315: * <code>expr</code> the portion wrapped with altSyntax (%{...})
316: * against stack when altSyntax is on, else the whole <code>expr</code>
317: * is evaluated against the stack.
318: * <p/>
319: * This method only supports the altSyntax. So this should be set to true.
320: * @param expr OGNL expression.
321: * @param toType the type expected to find.
322: * @return the Object found, or <tt>null</tt> if not found.
323: */
324: protected Object findValue(String expr, Class toType) {
325: if (altSyntax() && toType == String.class) {
326: return TextParseUtil.translateVariables('%', expr, stack);
327: } else {
328: if (altSyntax()) {
329: // does the expression start with %{ and end with }? if so, just cut it off!
330: if (expr.startsWith("%{") && expr.endsWith("}")) {
331: expr = expr.substring(2, expr.length() - 1);
332: }
333: }
334:
335: return getStack().findValue(expr, toType);
336: }
337: }
338:
339: /**
340: * Renders an action URL by consulting the {@link org.apache.struts2.dispatcher.mapper.ActionMapper}.
341: * @param action the action
342: * @param namespace the namespace
343: * @param method the method
344: * @param req HTTP request
345: * @param res HTTP response
346: * @param parameters parameters
347: * @param scheme http or https
348: * @param includeContext should the context path be included or not
349: * @param encodeResult should the url be encoded
350: * @return the action url.
351: */
352: @Deprecated
353: protected String determineActionURL(String action,
354: String namespace, String method, HttpServletRequest req,
355: HttpServletResponse res, Map parameters, String scheme,
356: boolean includeContext, boolean encodeResult) {
357: return determineActionURL(action, namespace, method, req, res,
358: parameters, scheme, includeContext, encodeResult,
359: false, true);
360: }
361:
362: /**
363: * Renders an action URL by consulting the {@link org.apache.struts2.dispatcher.mapper.ActionMapper}.
364: * @param action the action
365: * @param namespace the namespace
366: * @param method the method
367: * @param req HTTP request
368: * @param res HTTP response
369: * @param parameters parameters
370: * @param scheme http or https
371: * @param includeContext should the context path be included or not
372: * @param encodeResult should the url be encoded
373: * @param forceAddSchemeHostAndPort should the scheme host and port be added to the url no matter what
374: * @param escapeAmp should the ampersands used separate parameters be escaped or not
375: * @return the action url.
376: */
377: protected String determineActionURL(String action,
378: String namespace, String method, HttpServletRequest req,
379: HttpServletResponse res, Map parameters, String scheme,
380: boolean includeContext, boolean encodeResult,
381: boolean forceAddSchemeHostAndPort, boolean escapeAmp) {
382: String finalAction = findString(action);
383: String finalMethod = method != null ? findString(method)
384: : method;
385: String finalNamespace = determineNamespace(namespace,
386: getStack(), req);
387: ActionMapping mapping = new ActionMapping(finalAction,
388: finalNamespace, finalMethod, parameters);
389: String uri = actionMapper.getUriFromActionMapping(mapping);
390: return UrlHelper.buildUrl(uri, req, res, parameters, scheme,
391: includeContext, encodeResult,
392: forceAddSchemeHostAndPort, escapeAmp);
393: }
394:
395: /**
396: * Determines the namespace of the current page being renderdd. Useful for Form, URL, and href generations.
397: * @param namespace the namespace
398: * @param stack OGNL value stack
399: * @param req HTTP request
400: * @return the namepsace of the current page being rendered, is never <tt>null</tt>.
401: */
402: protected String determineNamespace(String namespace,
403: ValueStack stack, HttpServletRequest req) {
404: String result;
405:
406: if (namespace == null) {
407: result = TagUtils.buildNamespace(actionMapper, stack, req);
408: } else {
409: result = findString(namespace);
410: }
411:
412: if (result == null) {
413: result = "";
414: }
415:
416: return result;
417: }
418:
419: /**
420: * Pushes this component's parameter Map as well as the component itself on to the stack
421: * and then copies the supplied parameters over. Because the component's parameter Map is
422: * pushed before the component itself, any key-value pair that can't be assigned to componet
423: * will be set in the parameters Map.
424: *
425: * @param params the parameters to copy.
426: */
427: public void copyParams(Map params) {
428: stack.push(parameters);
429: stack.push(this );
430: try {
431: for (Iterator iterator = params.entrySet().iterator(); iterator
432: .hasNext();) {
433: Map.Entry entry = (Map.Entry) iterator.next();
434: String key = (String) entry.getKey();
435: stack.setValue(key, entry.getValue());
436: }
437: } finally {
438: stack.pop();
439: stack.pop();
440: }
441: }
442:
443: /**
444: * Constructs a string representation of the given exception.
445: * @param t the exception
446: * @return the exception as a string.
447: */
448: protected String toString(Throwable t) {
449: FastByteArrayOutputStream bout = new FastByteArrayOutputStream();
450: PrintWriter wrt = new PrintWriter(bout);
451: t.printStackTrace(wrt);
452: wrt.close();
453:
454: return bout.toString();
455: }
456:
457: /**
458: * Gets the parameters.
459: * @return the parameters. Is never <tt>null</tt>.
460: */
461: public Map getParameters() {
462: return parameters;
463: }
464:
465: /**
466: * Adds all the given parameters to this component's own parameters.
467: * @param params the parameters to add.
468: */
469: public void addAllParameters(Map params) {
470: parameters.putAll(params);
471: }
472:
473: /**
474: * Adds the given key and value to this component's own parameter.
475: * <p/>
476: * If the provided key is <tt>null</tt> nothing happens.
477: * If the provided value is <tt>null</tt> any existing parameter with
478: * the given key name is removed.
479: * @param key the key of the new parameter to add.
480: * @param value the value assoicated with the key.
481: */
482: public void addParameter(String key, Object value) {
483: if (key != null) {
484: Map params = getParameters();
485:
486: if (value == null) {
487: params.remove(key);
488: } else {
489: params.put(key, value);
490: }
491: }
492: }
493:
494: /**
495: * Gets the id for referencing element.
496: * @return the id for referencing element.
497: */
498: public String getId() {
499: return id;
500: }
501:
502: @StrutsTagAttribute(description="id for referencing element. For UI and form tags it will be used as HTML id attribute")
503: public void setId(String id) {
504: if (id != null) {
505: this .id = findString(id);
506: }
507: }
508:
509: /**
510: * Overwrite to set if body shold be used.
511: * @return always false for this component.
512: */
513: public boolean usesBody() {
514: return false;
515: }
516: }
|