001    package hirondelle.web4j.action;
002    
003    import hirondelle.web4j.model.ModelUtil;
004    import hirondelle.web4j.util.WebUtil;
005    
006    /**
007     <span class="highlight">Response page served to the user at the end of an {@link Action}.</span>
008     
009     <P>Identifies the page as a resource. Does not include its content, but is rather  
010     a <em>reference</em> to the page. 
011     
012     <P>The {@link hirondelle.web4j.Controller} will either redirect or forward to the
013     resource identified by {@link #toString}, according to the value of {@link #getIsRedirect}.
014     The default value of {@link #getIsRedirect} varies according to constructor:
015    <ul>
016     <li>{@link #ResponsePage(String)} defaults to a <em>redirect</em>
017     <li>all other constructors default to a <em>forward</em>
018    </ul> 
019     
020     <P>These defaults are almost always the desired values. They can be changed, if 
021     necessary, by calling {@link #setIsRedirect}. 
022     
023     <P>See the <a href="http://www.javapractices.com/Topic181.cjp">forward-versus-redirect</a> 
024     topic on javapractices.com for more information.
025    */
026    public final class ResponsePage {
027    
028      /**
029       Constructor which uses the WEB4J template mechanism.
030       
031       @param aTitle text of the <tt>&lt;TITLE&gt;</tt> tag to be presented in the 
032       template page ; appended to <tt>aTemplateURL</tt> as a query parameter
033       @param aBodyJsp body of the templated page, where most of the content of interest 
034       lies ; appended to <tt>aTemplateURL</tt> as a query parameter
035       @param aTemplateURL identifies the templated page which dynamically includes 
036       <tt>aBodyJsp</tt> in its content. Some applications will have only a single 
037       template for the entire application. 
038      */
039      public ResponsePage(String aTitle, String aBodyJsp, String aTemplateURL){
040        this(aTitle, aBodyJsp, aTemplateURL, null);
041      }  
042    
043      /**
044       Constructor which uses the WEB4J template mechanism.
045       
046       <P><span class="highlight">
047       This constructor allows for an unusual but useful policy : placing JSPs in the 
048       same directory as related code, under <tt>WEB-INF/classes</tt>, instead 
049       of under the document root of the application.</span> 
050       Such a style is beneficial since it allows all (or nearly all) items related 
051       to a given feature to be placed in the same directory - JSPs, Action,
052       Model Objects, Data Access Objects, and <tt>.sql</tt> files.
053       This is the recommended style. It allows 
054       <a href="http://www.javapractices.com/Topic205.cjp">package-by-feature</a>.
055       
056       @param aTitle text of the <tt>&lt;TITLE&gt;</tt> tag to be presented in the 
057       template page ; appended to <tt>aTemplateURL</tt> as a query parameter
058       @param aBodyJsp body of the templated page, where most of the content of interest 
059       lies ; appended to <tt>aTemplateURL</tt> as a query parameter
060       @param aTemplateURL identifies the templated page which dynamically includes 
061       <tt>aBodyJsp</tt> in its content
062       @param aClass class literal of any java class related to the given feature; the 
063       <em>package</em> of this class will be used to construct the 'real' path to <tt>aBodyJsp</tt>, 
064       as in '<tt>WEB-INF/classes/&lt;package-as-directory-path&gt;/&lt;aBodyJsp&gt;</tt>'. These  
065       paths are completely internal, are known only to the {@link hirondelle.web4j.Controller}, and are 
066       never visible to the user in the URL. 
067      */
068      public ResponsePage(String aTitle, String aBodyJsp, String aTemplateURL, Class<?> aClass){
069        fIsRedirect = FOR_FORWARD;
070        String url = WebUtil.setQueryParam(aTemplateURL, "TTitle", aTitle);
071        url = WebUtil.setQueryParam(url, "TBody", getPathPrefix(aClass) + aBodyJsp);
072        fURL = url;
073        fIsBinary = false;
074      }  
075      
076      /**
077       Constructor for a non-templated response. 
078      
079       <P>This constructor does not use the WEB4J template mechanism.  
080       This constructor is used both for response pages that require a redirect, and for 
081       serving pages that do not use the templating mechanism.
082       
083       <P><tt>aURL</tt> identifies the resource which will be used by an {@link Action}
084       as its destination page. 
085       Example values for <tt>aURL</tt> :
086       <ul>
087       <li><tt>ViewAccount.do</tt> (suitable for a redirect)
088       <li><tt>/ProblemHasBeenLogged.html</tt> (suitable for a forward to an item 
089       which is <em>not</em> templated)
090       </ul> 
091       
092       <P>This constructor returns a redirect. If a forward is desired, call {@link #setIsRedirect(Boolean)}.
093      */
094      public ResponsePage(String aURL) {
095        this(aURL, FOR_REDIRECT);
096      }
097      
098      /**
099        Constructor for a non-templated response located under <tt>/WEB-INF/</tt>.
100        
101       <P><span class="highlight">
102       This constructor allows for an unusual but useful policy : placing JSPs in the 
103       same directory as related code, under <tt>WEB-INF/classes</tt>, instead 
104       of under the document root of the application.</span> 
105       Such a style is beneficial since it allows all (or nearly all) items related 
106       to a given feature to be placed in the same directory - JSPs, Action,
107       Model Objects, Data Access Objects, and <tt>.sql</tt> files.
108       This is the recommended style. It allows 
109       <a href="http://www.javapractices.com/Topic205.cjp">package-by-feature</a>.
110        
111       <P>This constructor defaults the response to a forward operation. 
112        
113       @param aJsp simple name of a JSP, as in 'view.jsp' 
114       @param aClass class literal of any java class in the same package as the given JSP.
115       The <em>package</em> of this class will be used to construct the 'real' path to the JSP, 
116       as in <PRE>WEB-INF/classes/&lt;package-as-directory-path&gt;/&lt;aJsp&gt;</PRE>These  
117       paths are completely internal, are known only to the {@link hirondelle.web4j.Controller}, and are 
118       never visible to the user in the URL. 
119       */
120      public ResponsePage(String aJsp, Class<?> aClass){
121        fIsRedirect = FOR_FORWARD;
122        fURL = getPathPrefix(aClass) + aJsp;
123        fIsBinary = false;
124      }
125    
126      /**
127       Factory method for binary responses. 
128       An example of a binary reponse is serving a report in <tt>.pdf</tt> format.
129       
130       <P>With such <tt>ResponsePage</tt>s, the normal templating mechanism is not 
131       available (since it is based on text), and no forward/redirect is performed by the 
132       <tt>Controller</tt>. In essence, the <tt>Action</tt> becomes entirely responsible 
133       for generating the response. 
134       
135       <P>When serving a binary response, your <tt>Action</tt> will typically : 
136       <ul>
137       <li>set the <tt>content-type</tt> response header.
138       <li>generate the output stream containing the desired binary content.
139       <li>close any resources, if necessary. For example, if generating a chart dynamically, then you 
140       may need to recover resources used in the generation of an image.
141       </ul>
142      */
143      public static ResponsePage withBinaryData(){
144        return new ResponsePage();
145      }
146      
147      /** Return <tt>true</tt> only if {@link #withBinaryData()} was called.  */
148      public Boolean hasBinaryData(){
149        return fIsBinary;
150      }
151    
152      /**
153       Return the URL passed to the constructor, with any query parameters added by 
154       {@link #appendQueryParam} appended on the end.
155      */
156      @Override public String toString() { 
157        return fURL;  
158      } 
159    
160      /** See class comment. */
161      public Boolean getIsRedirect(){
162        return fIsRedirect;
163      }
164      
165      /**
166       Return a new <tt>ResponsePage</tt>, having the specified forward/redirect behavior.
167       See class comment.
168        
169       <P>This method returns a new <tt>ResponsePage</tt> having the updated URL. 
170       (This will ensure that <tt>ResponsePage</tt> objects are immutable.) 
171      */
172      public ResponsePage setIsRedirect(Boolean aValue){
173        return new ResponsePage(fURL, aValue);    
174      }
175    
176      /**
177       Append a query parameter to the URL of a <tt>ResponsePage</tt>.
178       
179       <P>This method returns a new <tt>ResponsePage</tt> having the updated URL. 
180       (This will ensure that <tt>ResponsePage</tt> objects are immutable.) 
181       <P>This method uses {@link WebUtil#setQueryParam}. 
182      */
183      public ResponsePage appendQueryParam(String aParamName, String aParamValue){
184        String newURL = WebUtil.setQueryParam(fURL, aParamName, aParamValue);
185        return new ResponsePage(newURL, fIsRedirect);
186      }
187      
188      @Override public boolean equals(Object aThat){
189        Boolean result = ModelUtil.quickEquals(this, aThat);
190        if ( result == null ){
191          ResponsePage that = (ResponsePage) aThat;
192          result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
193        }
194        return result;    
195      }
196      
197      @Override public int hashCode(){
198        return ModelUtil.hashCodeFor(getSignificantFields());
199      }
200       
201      // PRIVATE //
202      private final String fURL;
203      private final Boolean fIsRedirect;
204      private final Boolean fIsBinary;
205      private static final Boolean FOR_REDIRECT = Boolean.TRUE;
206      private static final Boolean FOR_FORWARD = Boolean.FALSE;
207      
208      private String getPathPrefix(Class aClass){
209        String result = "";
210        if ( aClass != null ){
211          result = "/WEB-INF/classes/" + aClass.getPackage().getName().replace('.', '/') + "/"; 
212        }
213        return result;
214      }
215      
216      /** Private constructor. */
217      private ResponsePage(String aURL, Boolean aIsRedirect){
218        fIsRedirect = aIsRedirect;
219        fURL = aURL;
220        fIsBinary = false;
221      }
222    
223      /** Private constructor for binary responses. */
224      private ResponsePage(){
225        fIsRedirect = FOR_FORWARD; //not really used
226        fURL = null; //not usd
227        fIsBinary = true;
228      }
229      
230      private Object[] getSignificantFields(){
231        return new Object[] {fURL, fIsRedirect, fIsBinary};
232      }
233    }