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