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