001 package hirondelle.web4j.ui.tag;
002
003 import java.io.*;
004 import java.util.logging.*;
005 import javax.servlet.Servlet;
006 import javax.servlet.http.HttpServletRequest;
007 import javax.servlet.http.HttpServletResponse;
008 import javax.servlet.jsp.JspException;
009 import javax.servlet.jsp.tagext.SimpleTagSupport;
010 import javax.servlet.jsp.PageContext;
011 import javax.servlet.jsp.tagext.JspFragment;
012 import javax.servlet.jsp.JspContext;
013
014 import hirondelle.web4j.util.Util;
015
016 /**
017 Base class for implementing custom JSP tags.
018
019 <P>The custom tag can optionally have a body. The <tt>.tld</tt> entry for these tags must
020 have their <tt>body-content</tt> set to <tt>scriptless</tt>.
021
022 <P>Concrete subclasses of this class perform these tasks :
023 <ul>
024 <li>implement <tt>setXXX</tt> methods, one for each tag attribute;
025 each <tt>setXXX</tt> should validate its argument.
026 <li>optionally override the {@link #crossCheckAttributes} method, to
027 perform validations depending on more than one attribute.
028 <li>implement {@link #getEmittedText}, to return the text to be included in markup.
029 </ul>
030 */
031 public abstract class TagHelper extends SimpleTagSupport {
032
033 /**
034 <b>Template</b> method which calls {@link #getEmittedText(String)}.
035
036 <P>The body of this tag is evaluated, passed to {@link #getEmittedText(String)},
037 and the result is then written to the JSP output writer. In addition, this method will call
038 {@link #crossCheckAttributes()} at the start of processing.
039 */
040 @Override public final void doTag() throws JspException {
041 try {
042 crossCheckAttributes();
043 getJspContext().getOut().write(getEmittedText(getBody()));
044 }
045 catch (Throwable ex){
046 fLogger.severe("Cannot execute custom tag. " + Util.quote(ex));
047 throw new JspException("Cannot execute custom tag.", ex);
048 }
049 }
050
051 /**
052 Return the text this tag will display in the resulting web page.
053
054 @param aOriginalBody is the evaluated body of this tag. If there is no body, or
055 if the body is present but empty, then it is <tt>null</tt>.
056 @return the text to display in the resulting web page.
057 */
058 abstract protected String getEmittedText(String aOriginalBody) throws JspException, IOException;
059
060 /**
061 Perform validations that apply to more than one attribute.
062
063 <P>This default implementation does nothing.
064
065 <P>Validations that apply to a single attribute should be performed in its
066 corresponding <tt>setXXX</tt> method.
067
068 <P>If a problem is detected, subclasses must emit a <tt>RuntimeException</tt>
069 describing the problem. If all validations apply to only to a single attribute,
070 then this method should not be overridden.
071 */
072 protected void crossCheckAttributes() {
073 //do nothing in this default impl
074 }
075
076 /** Return the underlying {@link HttpServletRequest}. */
077 protected final HttpServletRequest getRequest(){
078 return (HttpServletRequest)getPageContext().getRequest();
079 }
080
081 /** Return the underlying {@link HttpServletResponse}. */
082 protected final HttpServletResponse getResponse(){
083 return (HttpServletResponse)getPageContext().getResponse();
084 }
085
086 /** Return the underlying {@link PageContext}. */
087 protected final PageContext getPageContext(){
088 JspContext jspContext = getJspContext();
089 return (PageContext)jspContext;
090 }
091
092 /**
093 Return the name of the JSP implementation class.
094 <P>Intended for debugging only.
095 */
096 protected final String getPageName(){
097 Servlet servlet = (Servlet)getPageContext().getPage();
098 return servlet.getClass().getName();
099 }
100
101 /**
102 Verify that an attribute value has content.
103
104 <P>If no content, then log at <tt>SEVERE</tt> and throw an unchecked exception.
105 */
106 protected final void checkForContent(String aAttributeName, String aAttributeValue){
107 if( ! Util.textHasContent(aAttributeValue) ){
108 String message = Util.quote(aAttributeName) + " attribute must have a value.";
109 fLogger.severe(message);
110 throw new IllegalArgumentException(message);
111 }
112 }
113
114 // PRIVATE //
115
116 private static final Logger fLogger = Util.getLogger(TagHelper.class);
117
118 /**
119 Return the evaluated body of this tag.
120
121 <P>The body of this tag cannot contain scriptlets or scriptlet expressions.
122 If this tag has no body, or has an empty body, then <tt>null</tt> is returned.
123 */
124 private String getBody() throws IOException, JspException {
125 String result = null;
126 JspFragment body = getJspBody();
127 if( body != null ){
128 StringWriter writer = new StringWriter();
129 getJspBody().invoke(writer);
130 writer.flush();
131 result = writer.toString();
132 }
133 return result;
134 }
135 }