001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.components;
006:
007: import com.opensymphony.webwork.util.FastByteArrayOutputStream;
008: import com.opensymphony.webwork.views.jsp.TagUtils;
009: import com.opensymphony.webwork.views.util.ContextUtil;
010: import com.opensymphony.webwork.views.util.UrlHelper;
011: import com.opensymphony.webwork.dispatcher.mapper.ActionMapping;
012: import com.opensymphony.webwork.dispatcher.mapper.ActionMapper;
013: import com.opensymphony.webwork.dispatcher.mapper.ActionMapperFactory;
014: import com.opensymphony.webwork.WebWorkException;
015: import com.opensymphony.xwork.util.OgnlValueStack;
016: import com.opensymphony.xwork.util.TextParseUtil;
017: import org.apache.commons.logging.Log;
018: import org.apache.commons.logging.LogFactory;
019:
020: import javax.servlet.http.HttpServletRequest;
021: import javax.servlet.http.HttpServletResponse;
022: import java.io.IOException;
023: import java.io.PrintWriter;
024: import java.io.Writer;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.Map;
028: import java.util.Stack;
029:
030: /**
031: * Base class to extend for UI components.
032: * <p/>
033: * This class is a good extension point when building reuseable UI components.
034: *
035: * @author plightbo
036: * @author tm_jee
037: *
038: * @version $Date: 2007-02-16 08:15:36 +0100 (Fri, 16 Feb 2007) $ $Id: Component.java 2846 2007-02-16 07:15:36Z tm_jee $
039: */
040: public class Component {
041: private static final Log LOG = LogFactory.getLog(Component.class);
042:
043: public static final String COMPONENT_STACK = "__component_stack";
044:
045: protected OgnlValueStack stack;
046: protected Map parameters;
047: protected String id;
048:
049: /**
050: * Constructor.
051: *
052: * @param stack OGNL value stack.
053: */
054: public Component(OgnlValueStack stack) {
055: this .stack = stack;
056: this .parameters = new HashMap();
057: getComponentStack().push(this );
058: }
059:
060: /**
061: * Get's the name of this component.
062: * @return the name of this component.
063: */
064: private String getComponentName() {
065: Class c = getClass();
066: String name = c.getName();
067: int dot = name.lastIndexOf('.');
068:
069: return name.substring(dot + 1).toLowerCase();
070: }
071:
072: /**
073: * Get's the OGNL value stack assoicated with this component.
074: * @return the OGNL value stack assoicated with this component.
075: */
076: public OgnlValueStack getStack() {
077: return stack;
078: }
079:
080: /**
081: * Get's the component stack of this component.
082: * @return the component stack of this component, never <tt>null</tt>.
083: */
084: public Stack getComponentStack() {
085: Stack componentStack = (Stack) stack.getContext().get(
086: COMPONENT_STACK);
087: if (componentStack == null) {
088: componentStack = new Stack();
089: stack.getContext().put(COMPONENT_STACK, componentStack);
090: }
091: return componentStack;
092: }
093:
094: /**
095: * Callback for the start tag of this component.
096: * Should the body be evaluated?
097: *
098: * @param writer the output writer.
099: * @return true if the body should be evaluated
100: */
101: public boolean start(Writer writer) {
102: return true;
103: }
104:
105: /**
106: * Callback for the end tag of this component.
107: * Should the body be evaluated again?
108: * <p/>
109: * <b>NOTE:</b> will pop component stack.
110: * @param writer the output writer.
111: * @param body the rendered body.
112: * @return true if the body should be evaluated again
113: */
114: public boolean end(Writer writer, String body) {
115: return end(writer, body, true);
116: }
117:
118: /**
119: * Callback for the start tag of this component.
120: * Should the body be evaluated again?
121: * <p/>
122: * <b>NOTE:</b> has a parameter to determine to pop the component stack.
123: * @param writer the output writer.
124: * @param body the rendered body.
125: * @param popComponentStack should the component stack be popped?
126: * @return true if the body should be evaluated again
127: */
128: protected boolean end(Writer writer, String body,
129: boolean popComponentStack) {
130: assert (body != null);
131:
132: try {
133: writer.write(body);
134: } catch (IOException e) {
135: throw new WebWorkException(
136: "IOError while writing the body: " + e.getMessage(),
137: e);
138: }
139: if (popComponentStack) {
140: popComponentStack();
141: }
142: return false;
143: }
144:
145: /**
146: * Pops the component stack.
147: */
148: protected void popComponentStack() {
149: getComponentStack().pop();
150: }
151:
152: /**
153: * Finds the nearest ancestor of this component stack.
154: * @param clazz the class to look for, or if assignable from.
155: * @return the component if found, <tt>null</tt> if not.
156: */
157: protected Component findAncestor(Class clazz) {
158: Stack componentStack = getComponentStack();
159: int currPosition = componentStack.search(this );
160: if (currPosition >= 0) {
161: int start = componentStack.size() - currPosition - 1;
162:
163: //for (int i = componentStack.size() - 2; i >= 0; i--) {
164: for (int i = start; i >= 0; i--) {
165: Component component = (Component) componentStack.get(i);
166: if (clazz.isAssignableFrom(component.getClass())
167: && component != this ) {
168: return component;
169: }
170: }
171: }
172:
173: return null;
174: }
175:
176: /**
177: * Evaluates the OGNL stack to find a String value.
178: * @param expr OGNL expression.
179: * @return the String value found.
180: */
181: protected String findString(String expr) {
182: return (String) findValue(expr, String.class);
183: }
184:
185: /**
186: * Evaluates the OGNL stack to find a String value.
187: * <p/>
188: * If the given expression is <tt>null</tt/> a error is logged and a <code>WebWorkException</code> is thrown
189: * constructed with a messaged based on the given field and errorMsg paramter.
190: *
191: * @param expr OGNL expression.
192: * @param field field name used when throwing <code>WebWorkException</code>.
193: * @param errorMsg error message used when throwing <code>WebWorkException</code>.
194: * @return the String value found.
195: * @throws WebWorkException is thrown in case of expression is <tt>null</tt>.
196: */
197: protected String findString(String expr, String field,
198: String errorMsg) {
199: if (expr == null) {
200: throw fieldError(field, errorMsg, null);
201: } else {
202: return findString(expr);
203: }
204: }
205:
206: /**
207: * Constructs?a <code>WebWorkException</code> based on the given information.
208: * <p/>
209: * A message is constructed and logged at ERROR level before being returned
210: * as a <code>WebWorkException</code>.
211: * @param field field name used when throwing <code>WebWorkException</code>.
212: * @param errorMsg error message used when throwing <code>WebWorkException</code>.
213: * @param e the caused exception, can be <tt>null</tt>.
214: * @return the constructed <code>WebWorkException</code>.
215: */
216: protected WebWorkException fieldError(String field,
217: String errorMsg, Exception e) {
218: String msg = "tag '"
219: + getComponentName()
220: + "', field '"
221: + field
222: + (id != null ? "', id '" + id : "")
223: + (parameters != null && parameters.containsKey("name") ? "', name '"
224: + parameters.get("name")
225: : "") + "': " + errorMsg;
226: if (e == null) {
227: LOG.error(msg);
228: return new WebWorkException(msg);
229: } else {
230: LOG.error(msg, e);
231: return new WebWorkException(msg, e);
232: }
233: }
234:
235: /**
236: * Finds a value from the OGNL stack based on the given expression.
237: * @param expr the expression. Returns <tt>null</tt> if expr is null.
238: * @return the value, <tt>null</tt> if not found.
239: */
240: protected Object findValue(String expr) {
241: if (expr == null) {
242: return null;
243: }
244:
245: if (altSyntax()) {
246: // does the expression start with %{ and end with }? if so, just cut it off!
247: if (expr.startsWith("%{") && expr.endsWith("}")) {
248: expr = expr.substring(2, expr.length() - 1);
249: }
250: }
251:
252: return getStack().findValue(expr);
253: }
254:
255: /**
256: * Is the altSyntax enabled?
257: * <p/>
258: * See <code>webwork.properties</code> where the altSyntax flag is defined.
259: * <b>Note:</b> Since WebWork 2.17 the altSyntax is default <tt>true</tt>.
260: * @return true if the altSyntax is enabled.
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: * If the given expression is <tt>null</tt/> a error is logged and a <code>WebWorkException</code> is thrown
270: * constructed with a messaged based on the given field and errorMsg paramter.
271: *
272: * @param expr OGNL expression.
273: * @param field field name used when throwing <code>WebWorkException</code>.
274: * @param errorMsg error message used when throwing <code>WebWorkException</code>.
275: * @return the Object found, is never <tt>null</tt>.
276: * @throws WebWorkException is thrown in case of not found in the OGNL stack, or expression is <tt>null</tt>.
277: */
278: protected Object findValue(String expr, String field,
279: 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.
301: * <p/>
302: * This method only supports the altSyntax. So this should be set to true.
303: * @param expr OGNL expression.
304: * @param toType the type expected to find.
305: * @return the Object found, or <tt>null</tt> if not found.
306: */
307: protected Object findValue(String expr, Class toType) {
308: if (altSyntax() && toType == String.class) {
309: return TextParseUtil.translateVariables('%', expr, stack);
310: } else {
311: if (altSyntax()) {
312: // does the expression start with %{ and end with }? if so, just cut it off!
313: if (expr.startsWith("%{") && expr.endsWith("}")) {
314: expr = expr.substring(2, expr.length() - 1);
315: }
316: }
317:
318: return getStack().findValue(expr, toType);
319: }
320: }
321:
322: /**
323: * Renders an action URL by consulting the {@link com.opensymphony.webwork.dispatcher.mapper.ActionMapper}.
324: * @param action the action
325: * @param namespace the namespace
326: * @param method the method
327: * @param req HTTP request
328: * @param res HTTP response
329: * @param parameters parameters
330: * @param scheme http or https
331: * @param includeContext should the context path be included or not
332: * @param encodeResult should the url be encoded
333: * @return the action url.
334: */
335: protected String determineActionURL(String action,
336: String namespace, String method, HttpServletRequest req,
337: HttpServletResponse res, Map parameters, String scheme,
338: boolean includeContext, boolean encodeResult,
339: boolean escapeXml) {
340: String finalAction = findString(action);
341: String finalNamespace = determineNamespace(namespace,
342: getStack(), req);
343: ActionMapping mapping = new ActionMapping(finalAction,
344: finalNamespace, method, parameters);
345: ActionMapper mapper = ActionMapperFactory.getMapper();
346: String uri = mapper.getUriFromActionMapping(mapping);
347: return UrlHelper.buildUrl(uri, req, res, parameters, scheme,
348: includeContext, encodeResult, false, escapeXml);
349: }
350:
351: /**
352: * Determines the namespace of the current page being renderdd. Useful for Form, URL, and href generations.
353: * @param namespace the namespace
354: * @param stack OGNL value stack
355: * @param req HTTP request
356: * @return the namepsace of the current page being rendered, is never <tt>null</tt>.
357: */
358: protected String determineNamespace(String namespace,
359: OgnlValueStack stack, HttpServletRequest req) {
360: String result;
361:
362: if (namespace == null) {
363: result = TagUtils.buildNamespace(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
388: .hasNext();) {
389: Map.Entry entry = (Map.Entry) iterator.next();
390: String key = (String) entry.getKey();
391: stack.setValue(key, entry.getValue());
392: }
393: } finally {
394: stack.pop();
395: stack.pop();
396: }
397: }
398:
399: /**
400: * Constructs a string representation of the given exception.
401: * @param t the exception
402: * @return the exception as a string.
403: */
404: protected String toString(Throwable t) {
405: FastByteArrayOutputStream bout = new FastByteArrayOutputStream();
406: PrintWriter wrt = new PrintWriter(bout);
407: t.printStackTrace(wrt);
408: wrt.close();
409:
410: return bout.toString();
411: }
412:
413: /**
414: * Get's the parameters.
415: * @return the parameters. Is never <tt>null</tt>.
416: */
417: public Map getParameters() {
418: return parameters;
419: }
420:
421: /**
422: * Add's all the given parameters to this componenets own parameters.
423: * @param params the parameters to add.
424: */
425: public void addAllParameters(Map params) {
426: parameters.putAll(params);
427: }
428:
429: /**
430: * Add's the given key and value to this components own parameter.
431: * <p/>
432: * If the provided key is <tt>null</tt> nothing happends.
433: * If the provided value is <tt>null</tt> any existing parameter with
434: * the given key name is removed.
435: * @param key the key of the new parameter to add.
436: * @param value the value assoicated with the key.
437: */
438: public void addParameter(String key, Object value) {
439: if (key != null) {
440: Map params = getParameters();
441:
442: if (value == null) {
443: params.remove(key);
444: } else {
445: params.put(key, value);
446: }
447: }
448: }
449:
450: /**
451: * Get's the id for referencing element.
452: * @return the id for referencing element.
453: */
454: public String getId() {
455: return id;
456: }
457:
458: /**
459: * id for referencing element. For UI and form tags it will be used as HTML id attribute
460: * @ww.tagattribute required="false"
461: */
462: public void setId(String id) {
463: if (id != null) {
464: this .id = findString(id);
465: }
466: }
467:
468: /**
469: * Overwrite to set if body shold be used.
470: * @return always false for this component.
471: */
472: public boolean usesBody() {
473: return false;
474: }
475:
476: }
|