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