2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shale.component;
19  
20  import javax.faces.application.FacesMessage;
21  import javax.faces.component.UIInput;
22  import javax.faces.context.FacesContext;
23  import javax.faces.el.ValueBinding;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.shale.faces.ShaleConstants;
28  import org.apache.shale.util.LoadBundle;
29  import org.apache.shale.util.Messages;
30  import org.apache.shale.util.TokenProcessor;
31  
32  /***
33   * <p>Component that renders a transaction token input field, and then
34   * validates it on a subsequent form submit. The token component must
35   * be the last input component child of the parent form to be processed.</p>
36   *
37   * $Id: Token.java 472288 2006-11-07 21:41:16Z rahul $
38   */
39  public class Token extends UIInput {
40  
41  
42      // -------------------------------------------------------- Static Variables
43  
44  
45      /***
46       * <p>Log instance for this class.</p>
47       */
48      private static final Log log = LogFactory.getLog(Token.class);
49  
50  
51      /***
52       * <p>The resource bundle <code>messageSummary</code> key.</p>
53       */
54      private static final String MESSAGE_SUMMARY_KEY = "token.summary.invalid";
55  
56      /***
57       * <p>The resource bundle <code>messageDetail</code> key.</p>
58       */
59      private static final String MESSAGE_DETAIL_KEY = "token.detail.invalid";
60  
61  
62      /***
63       * <p>Local component attribute under which we store the token value
64       * the first time it is generated.</p>
65       */
66      private static final String TOKEN_ATTRIBUTE_KEY = "org.apache.shale.Token.TOKEN_VALUE";
67  
68  
69      /***
70       * <p>Message resources for this class.</p>
71       */
72      private static Messages messages =
73        new Messages("org.apache.shale.resources.Bundle",
74                     Token.class.getClassLoader());
75  
76  
77      // ------------------------------------------------------------ Constructors
78  
79      /***
80       * <p>A validation message summary override that can be used to change the default
81       * validation message summary when the token verification fails.</p>
82       */
83      private String messageSummary = null;
84  
85      /***
86       * <p>Returns the validation <code>messageSummary</code> used to create a
87       * <code>FacesMessage.SEVERITY_ERROR</code>.</p>
88       */
89      public String getMessageSummary() {
90          if (null != messageSummary) {
91              return messageSummary;
92          }
93          ValueBinding valuebinding = getValueBinding("messageSummary");
94          if (valuebinding != null) {
95              return (String) valuebinding.getValue(getFacesContext());
96          } else {
97              return null;
98          }
99      }
100 
101     /***
102      * <p>Sets a <code>messageSummary</code> override used when reporting
103      * a token verification failure.</p>
104      *
105      * @param message The new message summary
106      */
107     public void setMessageSummary(String message) {
108        this.messageSummary = message;
109     }
110 
111 
112     /***
113      * <p>A validation message detail override that can be used to change the default
114      * validation message detail when the token verification fails.</p>
115      */
116     private String messageDetail = null;
117 
118     /***
119      * <p>Returns the validation <code>messageDetail</code> used to create a
120      * <code>FacesMessage.SEVERITY_ERROR</code>.</p>
121      */
122     public String getMessageDetail() {
123         if (null != messageDetail) {
124             return messageDetail;
125         }
126         ValueBinding valuebinding = getValueBinding("messageDetail");
127         if (valuebinding != null) {
128             return (String) valuebinding.getValue(getFacesContext());
129         } else {
130             return null;
131         }
132     }
133 
134     /***
135      * <p>Sets a <code>messageDetail</code> override used when reporting
136      * a token verification failure.</p>
137      *
138      * @param message The new message detail
139      */
140     public void setMessageDetail(String message) {
141        this.messageDetail = message;
142     }
143 
144 
145     /***
146      * <p>Create a default instance of this component.</p>
147      */
148     public Token() {
149         setRendererType("org.apache.shale.Token");
150     }
151 
152 
153     // -------------------------------------------------------------- Properties
154 
155 
156     /***
157      * <p>Return the component family for this component.</p>
158      */
159     public String getFamily() {
160         return "org.apache.shale.Token";
161     }
162 
163 
164     // --------------------------------------------------------- UIInput Methods
165 
166 
167     /***
168      * <p>Perform superclass validations, then ensure that the specified input
169      * value is acceptable at this point in time.</p>
170      *
171      * @param context <code>FacesContext</code> for the current request
172      */
173     public void validate(FacesContext context) {
174 
175         // If any of the other input components in this form triggered
176         // validation errors, we do NOT want to validate the token component
177         // here, because that would erase the saved token and prevent the
178         // subsequent valid resubmit from succeeding.
179         //
180         // WARNING - for this test to be successful, the token component must
181         // be the last input component child of the parent form to be
182         // processed
183         if (context.getMaximumSeverity() != null) {
184             return;
185         }
186 
187         super.validate(context);
188         String token = (String) getValue();
189         if (log.isDebugEnabled()) {
190             log.debug("Validating token '" + token + "'");
191         }
192         TokenProcessor tp = getTokenProcessor(context);
193         if (!tp.verify(context, token)) {
194             if (log.isDebugEnabled()) {
195                 log.debug("  Validation failed!");
196             }
197             setValid(false);
198             String summary = getErrorSummaryMessage(context);
199             String detail = getErrorDetailMessage(context);
200             FacesMessage message = new FacesMessage(summary, detail);
201             message.setSeverity(FacesMessage.SEVERITY_ERROR);
202             context.addMessage(getClientId(context), message);
203         }
204 
205     }
206 
207     /***
208      * <p>Returns the validation summary message.  The validation
209      * <code>messageSummary</code> is evaluated in the following order:</p>
210      * <ol>
211      * <li>The <code>messageSummary</code> property on the {@link Token}
212      *     component</li>
213      * <li>A custom resource bundled registered in
214      *     <code>faces-config.xml</code> using the message key of
215      *     <strong>token.summary.invalid</strong>.
216      * <p><blockquote><pre>
217      *    &lt;application&gt;
218      *       &lt;messageSummary-bundle&gt;org.acme.resources.Bundle&lt;/messageSummary-bundle&gt;
219      *    &lt;/application&gt;
220      *</pre></blockquote></p></li>
221      * <li>The default will be taken from
222      *     <strong>org.apache.shale.resources.Bundle</strong> packaged
223      *     in the core Shale java archive.  The default message summary
224      *     is "<strong>Invalid resubmit of the same form</strong>".</li>
225      *</ol>
226      *
227      * @param context faces context
228      * @return invalid token message
229      */
230     private String getErrorSummaryMessage(FacesContext context) {
231 
232         String msg = getMessageSummary();
233         if (msg == null) {
234             String bundleName = context.getApplication().getMessageBundle();
235             if (bundleName != null) {
236                 LoadBundle loadBundle = new LoadBundle(bundleName);
237                 msg = (String) loadBundle.getMap().get(MESSAGE_SUMMARY_KEY);
238             }
239         }
240         if (msg == null) {
241             msg = messages.getMessage(MESSAGE_SUMMARY_KEY);
242         }
243         return msg;
244 
245     }
246 
247 
248     /***
249      * <p>Returns the validation detail message.  The validation
250      * <code>messageDetail</code> is evaluated in the following order:</p>
251      * <ol>
252      * <li>The <code>messageDetail</code> property on the {@link Token}
253      *     component</li>
254      * <li>A custom resource bundled registered in
255      *     <code>faces-config.xml</code> using the message key of
256      *     <strong>token.detail.invalid</strong>.
257      * <p><blockquote><pre>
258      *    &lt;application&gt;
259      *       &lt;messageSummary-bundle&gt;org.acme.resources.Bundle&lt;/messageSummary-bundle&gt;
260      *    &lt;/application&gt;
261      * </pre></blockquote></p></li>
262      * <li>The default will be taken from
263      *     <strong>org.apache.shale.resources.Bundle</strong>
264      *     packaged in the core Shale java archive.</li>
265      * <li>The default message detail is
266      *     an empty string, ""</li>
267      *</ol>
268      *
269      * @param context faces context
270      * @return invalid token message
271      */
272     private String getErrorDetailMessage(FacesContext context) {
273 
274         String msg = getMessageDetail();
275         if (msg == null) {
276             String bundleName = context.getApplication().getMessageBundle();
277             if (bundleName != null) {
278                 LoadBundle loadBundle = new LoadBundle(bundleName);
279                 msg = (String) loadBundle.getMap().get(MESSAGE_DETAIL_KEY);
280             }
281         }
282         if (msg == null) {
283             msg = messages.getMessage(MESSAGE_DETAIL_KEY);
284         }
285         return msg;
286 
287     }
288 
289 
290     // ---------------------------------------------------------- Public Methods
291 
292 
293     /***
294      * <p>Return the transaction token value to be rendered for this occcurrence
295      * of this component.  As a side effect, the transaction token value will
296      * be saved for verification on a subsequent submit.</p>
297      */
298     public String getToken() {
299 
300         // Have we already generated a token value?  If so, use it
301         String value = (String) getAttributes().get(TOKEN_ATTRIBUTE_KEY);
302         if (value != null) {
303              return value;
304         }
305 
306         // Generate a new token value and cache it for reuse if the
307         // current view is rerendered
308         FacesContext context = FacesContext.getCurrentInstance();
309         TokenProcessor tp = getTokenProcessor(context);
310         String token = tp.generate(context);
311         getAttributes().put(TOKEN_ATTRIBUTE_KEY, token);
312         if (log.isDebugEnabled()) {
313             log.debug("Generating token '" + token + "'");
314         }
315         return token;
316 
317     }
318 
319 
320     // --------------------------------------------------------- Private Methods
321 
322 
323     /***
324      * <p>Retrieve the {@link TokenProcessor} instance for this application,
325      * creating and caching a new one if necessary.</p>
326      *
327      * @param context <code>FacesContext</code> for the current request
328      */
329      private TokenProcessor getTokenProcessor(FacesContext context) {
330 
331          TokenProcessor tp = (TokenProcessor) context.getExternalContext().
332            getApplicationMap().get(ShaleConstants.TOKEN_PROCESSOR);
333          if (tp == null) {
334              tp = new TokenProcessor();
335              context.getExternalContext().
336                getApplicationMap().put(ShaleConstants.TOKEN_PROCESSOR, tp);
337          }
338          return tp;
339 
340      }
341 
342     /***
343      * <p>Restores the components state.</p>
344      *
345      * @param context FacesContext for the current request
346      * @param obj State to be restored
347      */
348     public void restoreState(FacesContext context, Object obj) {
349         Object[] aobj = (Object[]) obj;
350         super.restoreState(context, aobj[0]);
351 
352         messageSummary = ((String) aobj[1]);
353         messageDetail = ((String) aobj[2]);
354     }
355 
356     /***
357      * <p>Saves the components state.</p>
358      *
359      * @param context FacesContext for the current request
360      */
361     public Object saveState(FacesContext context) {
362         Object[] aobj = new Object[3];
363         aobj[0] = super.saveState(context);
364         aobj[1] = messageSummary;
365         aobj[2] = messageDetail;
366 
367         return aobj;
368     }
369 
370 
371 }