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    }