001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.tags;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022:
023: import org.apache.beehive.netui.core.urls.URLRewriterService;
024: import org.apache.beehive.netui.script.ExpressionEvaluationException;
025: import org.apache.beehive.netui.tags.javascript.IScriptReporter;
026: import org.apache.beehive.netui.tags.javascript.ScriptContainer;
027: import org.apache.beehive.netui.tags.naming.FormDataNameInterceptor;
028: import org.apache.beehive.netui.tags.naming.INameInterceptor;
029: import org.apache.beehive.netui.tags.naming.IndexedNameInterceptor;
030: import org.apache.beehive.netui.tags.html.Form;
031: import org.apache.beehive.netui.util.Bundle;
032: import org.apache.beehive.netui.util.logging.Logger;
033: import org.apache.beehive.netui.pageflow.internal.InternalUtils;
034:
035: import javax.servlet.ServletRequest;
036: import javax.servlet.http.HttpServletRequest;
037: import javax.servlet.jsp.JspException;
038: import javax.servlet.jsp.JspWriter;
039: import javax.servlet.jsp.PageContext;
040: import javax.servlet.jsp.tagext.BodyTagSupport;
041: import javax.servlet.jsp.tagext.SimpleTagSupport;
042: import javax.servlet.jsp.tagext.Tag;
043: import java.io.IOException;
044: import java.util.*;
045:
046: /**
047: * AbstractBaseTag is the base tag for most of the NetUI tags. This tag provides default behavior
048: * and services for NetUI tags. There following categories of services are provided by this tag:
049: * <ul>
050: * <li><b>Generic Services</b> -- These are general services such as access to local, writting
051: * to the response, and writting attributes.</li>
052: * <li><b>Expression Management</b> -- This set of method provide evaluation and information about
053: * expressions. These methods allow tags to fully support expressions for attributes.</li>
054: * <li><b>Naming and NamingInterceptor Services</b> -- This set of methods will apply
055: * <code>INameInterceptor</code>s to a name to produce the name written out. In addition, it allows
056: * the URL Rewritter service to modify names.</li>
057: * <li><b>Attribute Rendering Support</b> -- This set of routine allow tags to keep simple attributes
058: * in a map that can be written into the generated markup. There are two types of attributes, attributes
059: * that contain expression and attributes that do not contain expression supported.</li>
060: * <li><b>Error Reporting</b> -- This set of routines will report errors within the tags. In development
061: * mode errors are reported in-page.</li>
062: * <li><b>JavaScript Services</b> -- This set of routines provide simple access for JavaScript generation.</li>
063: * </ul>
064: * @netui:tag
065: */
066: public abstract class AbstractClassicTag extends BodyTagSupport
067: implements INetuiTag {
068: //@todo: need to implement the flag to turn errors into JSP exceptions
069:
070: private static final Logger logger = Logger
071: .getInstance(AbstractClassicTag.class);
072:
073: /**
074: * This List represents the default naming chain for handling <code>dataSource</code> attributes. The list
075: * is a read-only list which may be used by any <code>dataSource</code> implementation.
076: */
077: public static final List DefaultNamingChain;
078:
079: /**
080: * This is the name of a request scoped attribute which creates a unique id for processing
081: * a request.
082: */
083: public static final String NETUI_UNIQUE_CNT = "netui.unique.id";
084:
085: /**
086: * This is a private formRewriter which is used by <code>qualifyName</code> to provide
087: * struts naming behavior.
088: */
089: private static final INameInterceptor formRewriter = new FormDataNameInterceptor();
090:
091: /**
092: * String constant for the empty string.
093: */
094: protected static final String EMPTY_STRING = "";
095:
096: // create the default naming chain.
097: static {
098: List l = new ArrayList(2);
099: l.add(new FormDataNameInterceptor());
100: l.add(new IndexedNameInterceptor());
101: DefaultNamingChain = Collections.unmodifiableList(l);
102: }
103:
104: private ErrorHandling _eh; // This class will track and handle errors
105:
106: /////////////////////////// Generic Services support ////////////////////////////
107:
108: /**
109: * Return the name of the tag. Used by error reporting to get the name of the tag.
110: * @return the name of the tag.
111: */
112: public abstract String getTagName();
113:
114: /**
115: * This is a method that will reinitialize all temporary state on a
116: * tag and should be called in the doEndTag method.
117: */
118: protected void localRelease() {
119: _eh = null;
120: }
121:
122: /**
123: * This method will return the user local of the request.
124: * @return the Locale object to use when rendering this tag
125: */
126: protected Locale getUserLocale() {
127: return InternalUtils.lookupLocale(pageContext.getRequest());
128: }
129:
130: /**
131: * This mehod will write the passed string to the response.
132: * @param string to be written to the response.
133: */
134: protected final void write(String string) {
135: JspWriter writer = pageContext.getOut();
136: try {
137: writer.print(string);
138: } catch (IOException e) {
139: logger.error(Bundle.getString("Tags_WriteException"), e);
140: org.apache.struts.util.RequestUtils.saveException(
141: (PageContext) pageContext, e);
142: }
143: }
144:
145: /////////////////////////// Naming and NamingInterceptor support ////////////////////////////
146:
147: /**
148: * Return an <code>List</code> which represents a chain of <code>INameInterceptor</code>
149: * objects. This method by default returns <code>null</code> and should be overridden
150: * by objects that support naming.
151: * @return an <code>List</code> that will contain <code>INameInterceptor</code> objects.
152: */
153: protected List getNamingChain() {
154: return null;
155: }
156:
157: /**
158: * This method walks all of the naming chains and allows them to rewrite the <code>name</code> parameter.
159: * After the naming chain processes the name, it will be passed to <code>rewriteName</code> for final processing.
160: * If the naming chaing returned from <code>getNamingChain</code> returns null, the name will be passed to
161: * <code>rewriteName</code> and returned. If there is an <code>ExpressionEvaluationException</code> thrown
162: * by a <code>INameInterceptor</code>, the error will be registered with the tag and <code>null</code> will
163: * be returned.
164: * @param name the name to rewrite
165: * @return the name after it was passed to all <code>INameInterceptor</code>s in the naming chain.
166: * @see #rewriteName
167: * @see org.apache.beehive.netui.tags.naming.INameInterceptor
168: */
169: protected String applyNamingChain(String name) throws JspException {
170: assert (name != null) : "The name parameter may not be null";
171:
172: List namingChain = getNamingChain();
173: if (namingChain == null)
174: return rewriteName(name);
175:
176: //if (logger.isDebugEnabled())
177: // logger.debug("rewrite name \"" + name + "\" on tag of type \"" + getClass().getName() + " with namingChain " +
178: // (namingChain != null ? "size " + namingChain.size() : "null"));
179:
180: try {
181: String newName = name;
182: int cnt = namingChain.size();
183: for (int i = 0; i < cnt; i++) {
184: //if (logger.isDebugEnabled())
185: // logger.debug("rewriteName: \"" + newName + "\" with INameInterceptor: " + namingChain.get(i).getClass().getName());
186:
187: newName = ((INameInterceptor) namingChain.get(i))
188: .rewriteName(newName, this );
189:
190: //if (logger.isDebugEnabled())
191: // logger.debug("rewrite result: " + newName);
192: }
193:
194: return rewriteName(newName);
195: } catch (ExpressionEvaluationException ee) {
196: // if there is an expression evaluation error set the error and return null;
197: logger.error(Bundle.getString(
198: "Tags_ExpressionQualifyingFailure", name));
199:
200: // create the expression info an add it to the error tracking
201: EvalErrorInfo info = new EvalErrorInfo();
202: info.evalExcp = ee;
203: info.expression = name;
204: info.attr = "dataSource";
205: info.tagType = getTagName();
206:
207: // report the error
208: registerTagError(info);
209: return null;
210: }
211: }
212:
213: /**
214: * An internal method that allows a tag to qualify the <code>name</code> paramater by converting
215: * it from a struts style naming convention to an explicit databinding expression. The qualified
216: * name will be returned. This method may report an error if there is an error in the expression.
217: * @param name the name to be qualified
218: * @return the name which has been qualified
219: * @throws JspException throws a JspException if in-page error reporting is turned off.
220: * @see org.apache.beehive.netui.tags.naming.FormDataNameInterceptor
221: */
222: protected String qualifyAttribute(String name) throws JspException {
223: if (name == null)
224: return null;
225:
226: // if this is a Struts style name, convert it to an expression
227: try {
228: name = formRewriter.rewriteName(name, this );
229: } catch (ExpressionEvaluationException e) {
230: String s = Bundle.getString(
231: "Tags_DataSourceExpressionError", new Object[] {
232: name, e.toString() });
233: registerTagError(s, null);
234: }
235: return name;
236: }
237:
238: /**
239: * This method will rewrite the name (id) by passing it to the
240: * URL Rewritter and getting back a value.
241: * @param name the name that will be rewritten
242: * @return a name that has been rewritten by the URLRewriterService.
243: */
244: final protected String rewriteName(String name) {
245: return URLRewriterService.getNamePrefix(pageContext
246: .getServletContext(), pageContext.getRequest(), name)
247: + name;
248: }
249:
250: /**
251: * This method will generate a real id based upon the passed in tagId. The generated
252: * id will be constucted by searching upward for all the script containers that have a
253: * scope id set. These will form a fully qualified id.
254: * @param tagId The base tagId set on a tag
255: * @return an id value formed by considering all of the scope id's found in the tag hierarchy.
256: */
257: final protected String getIdForTagId(String tagId) {
258: HttpServletRequest req = (HttpServletRequest) pageContext
259: .getRequest();
260: ArrayList/*<String>*/list = (ArrayList/*<String>*/) RequestUtils
261: .getOuterAttribute(req, ScriptContainer.SCOPE_ID);
262: if (list == null)
263: return tagId;
264: InternalStringBuilder sb = new InternalStringBuilder();
265: for (int i = 0; i < list.size(); i++) {
266: sb.append((String) list.get(i));
267: sb.append('.');
268: }
269: sb.append(tagId);
270: return sb.toString();
271:
272: /*
273: Tag tag = this;
274: while (tag != null) {
275: if (tag instanceof ScriptContainer) {
276: String sid = ((ScriptContainer) tag).getIdScope();
277: if (sid != null) {
278: tagId = sid + "." + tagId;
279: }
280: }
281: tag = tag.getParent();
282: }
283: return tagId;
284: */
285: }
286:
287: /////////////////////////// Generic Attribute Setting Support ////////////////////////////
288:
289: /**
290: * Report an error if the value of <code>attrValue</code> is equal to the empty string, otherwise return
291: * that value. If <code>attrValue</code> is equal to the empty string, an error is registered and
292: * null is returned.
293: * @param attrValue The value to be checked for the empty string
294: * @param attrName The name of the attribute
295: * @return either the attrValue if it is not the empty string or null
296: * @throws JspException A JspException will be thrown if inline error reporting is turned off.
297: */
298: protected final String setRequiredValueAttribute(String attrValue,
299: String attrName) throws JspException {
300: assert (attrValue != null) : "parameter 'attrValue' must not be null";
301: assert (attrName != null) : "parameter 'attrName' must not be null";
302:
303: if ("".equals(attrValue)) {
304: String s = Bundle.getString("Tags_AttrValueRequired",
305: new Object[] { attrName });
306: registerTagError(s, null);
307: return null;
308: }
309: return attrValue;
310: }
311:
312: /**
313: * Filter out the empty string value and return either the value or null. When the value of
314: * <code>attrValue</code> is equal to the empty string this will return null, otherwise it will
315: * return the value of <code>attrValue</code>.
316: * @param attrValue This is the value we will check for the empty string.
317: * @return either the value of attrValue or null
318: */
319: protected final String setNonEmptyValueAttribute(String attrValue) {
320: return ("".equals(attrValue)) ? null : attrValue;
321: }
322:
323: /////////////////////////// Generic Error Reporting Support ////////////////////////////
324:
325: /**
326: * This is a simple routine which will call the error reporter if there is an
327: * error and then call local release before returning the <code>returnValue</code>.
328: * This is a very common code sequence in the Classic Tags so we provide this routine.
329: * @param returnValue The value that will be returned.
330: * @return <code>returnValue</code> is always returned.
331: * @throws JspException
332: */
333: protected int reportAndExit(int returnValue) throws JspException {
334: if (hasErrors()) {
335: reportErrors();
336: }
337: localRelease();
338: return returnValue;
339: }
340:
341: /**
342: * This will report an error from a tag. The error will
343: * contain a message. If error reporting is turned off,
344: * the message will be returned and the caller should throw
345: * a JspException to report the error.
346: * @param message - the message to register with the error
347: * @throws JspException - if in-page error reporting is turned off this method will always
348: * throw a JspException.
349: */
350: public void registerTagError(String message, Throwable e)
351: throws JspException {
352: ErrorHandling eh = getErrorHandling();
353: eh.registerTagError(message, getTagName(), this , e);
354: }
355:
356: /**
357: * This will report an error from a tag. The error must
358: * be be an AbstractPageError.
359: * @param error The <code>AbstractPageError</code> to add to the error list.
360: * @throws JspException - if in-page error reporting is turned off this method will always
361: * throw a JspException.
362: */
363: public void registerTagError(AbstractPageError error)
364: throws JspException {
365: ErrorHandling eh = getErrorHandling();
366: eh.registerTagError(error, this );
367: }
368:
369: /**
370: * This method will return <code>true</code> if there have been any errors registered on this
371: * tag. Otherwise it returns <code>false</code>
372: * @return <code>true</code> if errors have been reported on this tag.
373: */
374: protected boolean hasErrors() {
375: return (_eh != null);
376: }
377:
378: /**
379: * This method will write out the <code>String</code> returned by <code>getErrorsReport</code> to the
380: * response output stream.
381: * @throws JspException if <code>write</code> throws an exception.
382: * @see #write
383: */
384: protected void reportErrors() throws JspException {
385: assert (_eh != null);
386: String err = _eh.getErrorsReport(getTagName());
387: IErrorCollector ec = (IErrorCollector) SimpleTagSupport
388: .findAncestorWithClass(this , IErrorCollector.class);
389: if (ec != null) {
390: ec.collectChildError(err);
391: } else {
392: write(err);
393: }
394: }
395:
396: /**
397: * This method will return a <code>String<code> that represents all of the errors that were
398: * registered for the tag. This method assumes that there are errors in the tag and asserts
399: * this is true. Code will typically call <code>hasErrors</code> before calling this method.
400: * @return A <code>String</code> that contains all of the errors registered on this tag.
401: */
402: protected String getErrorsReport() {
403: assert _eh != null;
404: return _eh.getErrorsReport(getTagName());
405: }
406:
407: /**
408: * This method will return an ErrorHandling instance.
409: * @return Return the ErrorHandling object
410: */
411: private ErrorHandling getErrorHandling() {
412: if (_eh == null) {
413: _eh = new ErrorHandling();
414: }
415: return _eh;
416: }
417:
418: /////////////////////////// JavaScript Support Support ////////////////////////////
419:
420: /**
421: * Return the closest <code>ScriptReporter</code> in the parental chain. Searching starts
422: * at this node an moves upward through the parental chain.
423: * @return a <code>ScriptReporter</code> or null if there is not one found.
424: */
425: protected IScriptReporter getScriptReporter() {
426: return (IScriptReporter) SimpleTagSupport
427: .findAncestorWithClass(this , IScriptReporter.class);
428: }
429:
430: /////////////////////////// Misc Features Support ////////////////////////////
431:
432: /**
433: * This method will generate the next unique int within the HTML tag.
434: * @param req the Request
435: * @return the next unique integer for this request.
436: */
437: protected int getNextId(ServletRequest req) {
438: Integer i = (Integer) RequestUtils.getOuterAttribute(
439: (HttpServletRequest) req, NETUI_UNIQUE_CNT);
440: if (i == null) {
441: i = new Integer(0);
442: }
443:
444: int ret = i.intValue();
445: RequestUtils.setOuterAttribute((HttpServletRequest) req,
446: NETUI_UNIQUE_CNT, new Integer(ret + 1));
447: return ret;
448: }
449:
450: /**
451: * Returns the closest parent form tag, or null if there is none.
452: */
453: protected Form getNearestForm() {
454: Tag parentTag = getParent();
455: while (parentTag != null) {
456: if (parentTag instanceof Form)
457: return (Form) parentTag;
458: parentTag = parentTag.getParent();
459: }
460: return null;
461: }
462: }
|