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 }