View Javadoc

1   /*
2    * $Id: ActionContextBase.java 421119 2006-07-12 04:49:11Z wsmoak $
3    *
4    * Copyright 2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts.chain.contexts;
19  
20  import org.apache.commons.chain.Context;
21  import org.apache.commons.chain.impl.ContextBase;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.struts.action.Action;
25  import org.apache.struts.action.ActionForm;
26  import org.apache.struts.action.ActionMessages;
27  import org.apache.struts.chain.Constants;
28  import org.apache.struts.config.ActionConfig;
29  import org.apache.struts.config.FormBeanConfig;
30  import org.apache.struts.config.ForwardConfig;
31  import org.apache.struts.config.ModuleConfig;
32  import org.apache.struts.util.MessageResources;
33  import org.apache.struts.util.TokenProcessor;
34  
35  import java.util.Locale;
36  import java.util.Map;
37  
38  /***
39   * <p> Provide an abstract but semi-complete implementation of ActionContext
40   * to serve as the base for concrete implementations. </p> <p> The abstract
41   * methods to implement are the accessors for the named states,
42   * <code>getApplicationScope</code>, <code>getRequestScope</code>, and
43   * <code>getSessionScope</code>. </p>
44   */
45  public abstract class ActionContextBase extends ContextWrapper
46      implements ActionContext {
47      /***
48       * @see Constants.ACTION_KEY
49       */
50      public static final String ACTION_KEY = Constants.ACTION_KEY;
51  
52      /***
53       * @see
54       */
55      public static final String ACTION_CONFIG_KEY = Constants.ACTION_CONFIG_KEY;
56  
57      /***
58       * @see Constants.ACTION_FORM_KEY
59       */
60      public static final String ACTION_FORM_KEY = Constants.ACTION_FORM_KEY;
61  
62      /***
63       * @see Constants.FORWARD_CONFIG_KEY
64       */
65      public static final String FORWARD_CONFIG_KEY =
66          Constants.FORWARD_CONFIG_KEY;
67  
68      /***
69       * @see Constants.MODULE_CONFIG_KEY
70       */
71      public static final String MODULE_CONFIG_KEY = Constants.MODULE_CONFIG_KEY;
72  
73      /***
74       * @see Constants.EXCEPTION_KEY
75       */
76      public static final String EXCEPTION_KEY = Constants.EXCEPTION_KEY;
77  
78      /***
79       * <p> Provide the default context attribute under which to store the
80       * ActionMessage cache for errors. </p>
81       */
82      public static final String ERROR_ACTION_MESSAGES_KEY = "errors";
83  
84      /***
85       * <p> Provide the default context attribute under which to store the
86       * ActionMessage cache. </p>
87       */
88      public static final String MESSAGE_ACTION_MESSAGES_KEY = "messages";
89  
90      /***
91       * @see Constants.MESSAGE_RESOURCES_KEY
92       */
93      public static final String MESSAGE_RESOURCES_KEY =
94          Constants.MESSAGE_RESOURCES_KEY;
95  
96      /***
97       * @see Constants.INCLUDE_KEY
98       */
99      public static final String INCLUDE_KEY = Constants.INCLUDE_KEY;
100 
101     /***
102      * @see Constants.LOCALE_KEY
103      */
104     public static final String LOCALE_KEY = Constants.LOCALE_KEY;
105 
106     /***
107      * @see Constants.CANCEL_KEY
108      */
109     public static final String CANCEL_KEY = Constants.CANCEL_KEY;
110 
111     /***
112      * @see Constants.VALID_KEY
113      */
114     public static final String VALID_KEY = Constants.VALID_KEY;
115 
116     /***
117      * Provide the default context attribute under which to store the
118      * transaction token key.
119      */
120     public static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY";
121 
122     /***
123      * Provide the default context attribute under which to store the token
124      * key.
125      */
126     public static final String TOKEN_KEY = "TOKEN_KEY";
127 
128     /***
129      * Store the TokenProcessor instance for this Context.
130      */
131     protected TokenProcessor token = null;
132 
133     /***
134      * Store the Log instance for this Context.
135      */
136     private Log logger = null;
137 
138     /***
139      * Instantiate ActionContextBase, wrapping the given Context.
140      *
141      * @param context Context to wrap
142      */
143     public ActionContextBase(Context context) {
144         super(context);
145         token = TokenProcessor.getInstance();
146         logger = LogFactory.getLog(this.getClass());
147     }
148 
149     /***
150      * Instantiate ActionContextBase, wrapping a default ContextBase
151      * instance.
152      */
153     public ActionContextBase() {
154         this(new ContextBase());
155     }
156 
157     // -------------------------------
158     // General Application Support
159     // -------------------------------
160     public void release() {
161         this.token = null;
162     }
163 
164     public abstract Map getApplicationScope();
165 
166     public abstract Map getRequestScope();
167 
168     public abstract Map getSessionScope();
169 
170     public Map getScope(String scopeName) {
171         if (REQUEST_SCOPE.equals(scopeName)) {
172             return this.getRequestScope();
173         }
174 
175         if (SESSION_SCOPE.equals(scopeName)) {
176             return this.getSessionScope();
177         }
178 
179         if (APPLICATION_SCOPE.equals(scopeName)) {
180             return this.getApplicationScope();
181         }
182 
183         throw new IllegalArgumentException("Invalid scope: " + scopeName);
184     }
185 
186     // -------------------------------
187     // General Struts properties
188     // -------------------------------
189     public void setAction(Action action) {
190         this.put(ACTION_KEY, action);
191     }
192 
193     public Action getAction() {
194         return (Action) this.get(ACTION_KEY);
195     }
196 
197     public void setActionForm(ActionForm form) {
198         this.put(ACTION_FORM_KEY, form);
199     }
200 
201     public ActionForm getActionForm() {
202         return (ActionForm) this.get(ACTION_FORM_KEY);
203     }
204 
205     public void setActionConfig(ActionConfig config) {
206         this.put(ACTION_CONFIG_KEY, config);
207     }
208 
209     public ActionConfig getActionConfig() {
210         return (ActionConfig) this.get(ACTION_CONFIG_KEY);
211     }
212 
213     public void setForwardConfig(ForwardConfig forward) {
214         this.put(FORWARD_CONFIG_KEY, forward);
215     }
216 
217     public ForwardConfig getForwardConfig() {
218         return (ForwardConfig) this.get(FORWARD_CONFIG_KEY);
219     }
220 
221     public void setInclude(String include) {
222         this.put(INCLUDE_KEY, include);
223     }
224 
225     public String getInclude() {
226         return (String) this.get(INCLUDE_KEY);
227     }
228 
229     public Boolean getFormValid() {
230         return (Boolean) this.get(VALID_KEY);
231     }
232 
233     public void setFormValid(Boolean valid) {
234         this.put(VALID_KEY, valid);
235     }
236 
237     public ModuleConfig getModuleConfig() {
238         return (ModuleConfig) this.get(MODULE_CONFIG_KEY);
239     }
240 
241     public void setModuleConfig(ModuleConfig config) {
242         this.put(MODULE_CONFIG_KEY, config);
243     }
244 
245     public Exception getException() {
246         return (Exception) this.get(EXCEPTION_KEY);
247     }
248 
249     public void setException(Exception e) {
250         this.put(EXCEPTION_KEY, e);
251     }
252 
253     // -------------------------------
254     // ActionMessage Processing
255     // -------------------------------
256     public void addMessages(ActionMessages messages) {
257         this.addActionMessages(MESSAGE_ACTION_MESSAGES_KEY, messages);
258     }
259 
260     public void addErrors(ActionMessages errors) {
261         this.addActionMessages(ERROR_ACTION_MESSAGES_KEY, errors);
262     }
263 
264     public ActionMessages getErrors() {
265         return (ActionMessages) this.get(ERROR_ACTION_MESSAGES_KEY);
266     }
267 
268     public ActionMessages getMessages() {
269         return (ActionMessages) this.get(MESSAGE_ACTION_MESSAGES_KEY);
270     }
271 
272     public void saveErrors(ActionMessages errors) {
273         this.saveActionMessages(ERROR_ACTION_MESSAGES_KEY, errors);
274     }
275 
276     public void saveMessages(ActionMessages messages) {
277         this.saveActionMessages(MESSAGE_ACTION_MESSAGES_KEY, messages);
278     }
279 
280     // ISSUE: do we want to add this to the public API?
281 
282     /***
283      * <p> Add the given messages to a cache stored in this Context, under
284      * key. </p>
285      *
286      * @param key      The attribute name for the message cache
287      * @param messages The ActionMessages to add
288      */
289     public void addActionMessages(String key, ActionMessages messages) {
290         if (messages == null) {
291             // bad programmer! *slap*
292             return;
293         }
294 
295         // get any existing messages from the request, or make a new one
296         ActionMessages requestMessages = (ActionMessages) this.get(key);
297 
298         if (requestMessages == null) {
299             requestMessages = new ActionMessages();
300         }
301 
302         // add incoming messages
303         requestMessages.add(messages);
304 
305         // if still empty, just wipe it out from the request
306         this.remove(key);
307 
308         // save the messages
309         this.saveActionMessages(key, requestMessages);
310     }
311 
312     // ISSUE: do we want to add this to the public API?
313 
314     /***
315      * <p> Save the given ActionMessages into the request scope under the
316      * given key, clearing the attribute if the messages are empty or null.
317      * </p>
318      *
319      * @param key      The attribute name for the message cache
320      * @param messages The ActionMessages to add
321      */
322     public void saveActionMessages(String key, ActionMessages messages) {
323         this.saveActionMessages(REQUEST_SCOPE, key, messages);
324     }
325 
326     /***
327      * <p>Save the given <code>messages</code> into the map identified by the
328      * given <code>scopeId</code> under the given <code>key</code>.</p>
329      *
330      * @param scopeId
331      * @param key
332      * @param messages
333      */
334     public void saveActionMessages(String scopeId, String key,
335         ActionMessages messages) {
336         Map scope = getScope(scopeId);
337 
338         if ((messages == null) || messages.isEmpty()) {
339             scope.remove(key);
340 
341             return;
342         }
343 
344         scope.put(key, messages);
345     }
346 
347     // ISSUE: Should we deprecate this method, since it is misleading?
348     // Do we need it for backward compatibility?
349 
350     /***
351      * <p> Adapt a legacy form of SaveMessages to the ActionContext API by
352      * storing the ActoinMessages under the default scope.
353      *
354      * @param scope    The scope for the internal cache
355      * @param messages ActionMesssages to cache
356      */
357     public void saveMessages(String scope, ActionMessages messages) {
358         this.saveMessages(messages);
359     }
360 
361     // -------------------------------
362     // Token Processing
363     // -------------------------------
364     // ISSUE: Should there be a getToken method?
365     // Is there a problem trying to map this method from Action
366     // to ActionContext when we aren't necessarily sure how token
367     // processing maps into a context with an ill-defined "session"?
368     // There's no getToken() method, but maybe there should be. *
369     public void saveToken() {
370         String token = this.generateToken();
371 
372         this.put(TRANSACTION_TOKEN_KEY, token);
373     }
374 
375     public String generateToken() {
376         return token.generateToken(getTokenGeneratorId());
377     }
378 
379     // ISSUE: The original implementation was based on the HttpSession
380     // identifier; what would be a way to do that without depending on the
381     // Servlet API?
382     // REPLY: uuid's
383     // http://java.sun.com/products/jini/2.0/doc/specs/api/net/jini/id/Uuid.html
384     protected String getTokenGeneratorId() {
385         return "";
386     }
387 
388     public boolean isTokenValid() {
389         return this.isTokenValid(false);
390     }
391 
392     public boolean isTokenValid(boolean reset) {
393         // Retrieve the transaction token from this session, and
394         // reset it if requested
395         String saved = (String) this.get(TRANSACTION_TOKEN_KEY);
396 
397         if (saved == null) {
398             return false;
399         }
400 
401         if (reset) {
402             this.resetToken();
403         }
404 
405         // Retrieve the transaction token included in this request
406         String token = (String) this.get(TOKEN_KEY);
407 
408         if (token == null) {
409             return false;
410         }
411 
412         return saved.equals(token);
413     }
414 
415     public void resetToken() {
416         this.remove(TRANSACTION_TOKEN_KEY);
417     }
418 
419     // -------------------------------
420     // Cancel Processing
421     // -------------------------------
422     public Boolean getCancelled() {
423         return (Boolean) this.get(CANCEL_KEY);
424     }
425 
426     public void setCancelled(Boolean cancelled) {
427         this.put(CANCEL_KEY, cancelled);
428     }
429 
430     // -------------------------------
431     // MessageResources Processing
432     // -------------------------------
433     public void setMessageResources(MessageResources messageResources) {
434         this.put(MESSAGE_RESOURCES_KEY, messageResources);
435     }
436 
437     public MessageResources getMessageResources() {
438         return (MessageResources) this.get(MESSAGE_RESOURCES_KEY);
439     }
440 
441     public MessageResources getMessageResources(String key) {
442         return (MessageResources) this.get(key);
443     }
444 
445     // -------------------------------
446     // Locale Processing
447     // -------------------------------
448     public void setLocale(Locale locale) {
449         this.put(LOCALE_KEY, locale);
450     }
451 
452     public Locale getLocale() {
453         return (Locale) this.get(LOCALE_KEY);
454     }
455 
456     // -------------------------------
457     // Convenience Methods: these are not part of the formal ActionContext API,
458     // but are likely to be commonly useful.
459     // -------------------------------
460 
461     /***
462      * <p> Provide the currently configured commons-logging <code>Log</code>
463      * instance. </p>
464      *
465      * @return Log instance for this context
466      */
467     public Log getLogger() {
468         return this.logger;
469     }
470 
471     /***
472      * <p> Set the commons-logging <code>Log</code> instance which should be
473      * used to LOG messages. This is initialized at instantiation time but may
474      * be overridden. Be advised not to set the value to null, as
475      * <code>ActionContextBase</code> uses the logger for some of its own
476      * operations. </p>
477      */
478     public void setLogger(Log logger) {
479         this.logger = logger;
480     }
481 
482     /***
483      * <p> Using this <code>ActionContext</code>'s default
484      * <code>ModuleConfig</code>, return an existing <code>ActionForm</code>
485      * in the specified scope, or create a new one and add it to the specified
486      * scope. </p>
487      *
488      * @param formName  The name attribute of our ActionForm
489      * @param scopeName The scope identier (request, session)
490      * @return The ActionForm for this request
491      * @throws IllegalAccessException If object cannot be created
492      * @throws InstantiationException If object cannot be created
493      * @see this.findOrCreateActionForm(String, String, ModuleConfig)
494      */
495     public ActionForm findOrCreateActionForm(String formName, String scopeName)
496         throws IllegalAccessException, InstantiationException {
497         return this.findOrCreateActionForm(formName, scopeName,
498             this.getModuleConfig());
499     }
500 
501     /***
502      * <p> In the context of the given <code>ModuleConfig</code> and this
503      * <code>ActionContext</code>, look for an existing
504      * <code>ActionForm</code> in the specified scope. If one is found, return
505      * it; otherwise, create a new instance, add it to that scope, and then
506      * return it. </p>
507      *
508      * @param formName  The name attribute of our ActionForm
509      * @param scopeName The scope identier (request, session)
510      * @return The ActionForm for this request
511      * @throws IllegalAccessException   If object cannot be created
512      * @throws InstantiationException   If object cannot be created
513      * @throws IllegalArgumentException If form config is missing from module
514      *                                  or scopeName is invalid
515      */
516     public ActionForm findOrCreateActionForm(String formName, String scopeName,
517         ModuleConfig moduleConfig)
518         throws IllegalAccessException, InstantiationException {
519         Map scope = this.getScope(scopeName);
520 
521         ActionForm instance;
522         FormBeanConfig formBeanConfig =
523             moduleConfig.findFormBeanConfig(formName);
524 
525         if (formBeanConfig == null) {
526             throw new IllegalArgumentException("No form config found under "
527                 + formName + " in module " + moduleConfig.getPrefix());
528         }
529 
530         instance = (ActionForm) scope.get(formName);
531 
532         // ISSUE: Can we recycle the existing instance (if any)?
533         if (instance != null) {
534             getLogger().trace("Found an instance in scope " + scopeName
535                 + "; test for reusability");
536 
537             if (formBeanConfig.canReuse(instance)) {
538                 return instance;
539             }
540         }
541 
542         ActionForm form = formBeanConfig.createActionForm(this);
543 
544         // ISSUE: Should we check this call to put?
545         scope.put(formName, form);
546 
547         return form;
548     }
549 }