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><TITLE></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><TITLE></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/<package-as-directory-path>/<aBodyJsp></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/<package-as-directory-path>/<aJsp></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 }