001: package com.opensymphony.webwork.interceptor;
002:
003: import java.io.Serializable;
004: import java.util.IdentityHashMap;
005: import java.util.Map;
006:
007: import org.apache.commons.logging.Log;
008: import org.apache.commons.logging.LogFactory;
009:
010: import com.opensymphony.webwork.ServletActionContext;
011: import com.opensymphony.webwork.WebWorkException;
012: import com.opensymphony.webwork.dispatcher.SessionMap;
013: import com.opensymphony.xwork.ActionContext;
014: import com.opensymphony.xwork.ActionInvocation;
015: import com.opensymphony.xwork.ActionProxy;
016: import com.opensymphony.xwork.interceptor.Interceptor;
017: import com.opensymphony.xwork.interceptor.PreResultListener;
018: import com.opensymphony.xwork.util.OgnlValueStack;
019:
020: /**
021: * <!-- START SNIPPET: description -->
022: *
023: * This is designed to solve a few simple issues related to wizard-like functionality in WebWork. One of those issues is
024: * that some applications have a application-wide parameters commonly used, such <i>pageLen</i> (used for records per
025: * page). Rather than requiring that each action check if such parameters are supplied, this interceptor can look for
026: * specified parameters and pull them out of the session.
027: *
028: * <p/> This works by setting listed properties at action start with values from session/application attributes keyed
029: * after the action's class, the action's name, or any supplied key. After action is executed all the listed properties
030: * are taken back and put in session or application context.
031: *
032: * <p/> To make sure that each execution of the action is consistent it makes use of session-level locking. This way it
033: * guarantees that each action execution is atomic at the session level. It doesn't guarantee application level
034: * consistency however there has yet to be enough reasons to do so. Application level consistency would also be a big
035: * performance overkill.
036: *
037: * <p/> Note that this interceptor takes a snapshot of action properties just before result is presented (using a {@link
038: * PreResultListener}), rather than after action is invoked. There is a reason for that: At this moment we know that
039: * action's state is "complete" as it's values may depend on the rest of the stack and specifically - on the values of
040: * nested interceptors.
041: *
042: * <!-- END SNIPPET: description -->
043: *
044: * <p/> <u>Interceptor parameters:</u>
045: *
046: * <!-- START SNIPPET: parameters -->
047: *
048: * <ul>
049: *
050: * <li>session - a list of action properties to be bound to session scope</li>
051: *
052: * <li>application - a list of action properties to be bound to application scope</li>
053: *
054: * <li>key - a session/application attribute key prefix, can contain following values:</li>
055: *
056: * <ul>
057: *
058: * <li>CLASS - that creates a unique key prefix based on action namespace and action class, it's a default value</li>
059: *
060: * <li>ACTION - creates a unique key prefix based on action namespace and action name</li>
061: *
062: * <li>any other value is taken literally as key prefix</li>
063: *
064: * </ul>
065: *
066: * <li>type - with one of the following</li>
067: *
068: * <ul>
069: *
070: * <li>start - means it's a start action of the wizard-like action sequence and all session scoped properties are reset
071: * to their defaults</li>
072: *
073: * <li>end - means that session scoped properties are removed from session after action is run</li>
074: *
075: * <li>any other value or no value means that it's in-the-middle action that is set with session properties before it's
076: * executed, and it's properties are put back to session after execution</li>
077: *
078: * </ul>
079: *
080: * <li>sessionReset - boolean value causing all session values to be reset to action's default values or application
081: * scope values, note that it is similliar to type="start" and in fact it does the same, but in our team it is sometimes
082: * semantically preferred. We use session scope in two patterns - sometimes there are wizzard-like action sequences that
083: * have start and end, and sometimes we just want simply reset current session values.</li>
084: *
085: * </ul>
086: *
087: * <!-- END SNIPPET: parameters -->
088: *
089: * <p/> <u>Extending the interceptor:</u>
090: *
091: * <p/>
092: *
093: * <!-- START SNIPPET: extending -->
094: *
095: * There are no know extension points for this interceptor.
096: *
097: * <!-- END SNIPPET: extending -->
098: *
099: * <p/> <u>Example code:</u>
100: *
101: * <pre>
102: * <!-- START SNIPPET: example -->
103: * <!-- As the filter and orderBy parameters are common for all my browse-type actions,
104: * you can move control to the scope interceptor. In the session parameter you can list
105: * action properties that are going to be automatically managed over session. You can
106: * do the same for application-scoped variables-->
107: * <action name="someAction" class="com.examples.SomeAction">
108: * <interceptor-ref name="basicStack"/>
109: * <interceptor-ref name="hibernate"/>
110: * <interceptor-ref name="scope">
111: * <param name="session">filter,orderBy</param>
112: * <param name="autoCreateSession">true</param>
113: * </interceptor-ref>
114: * <result name="success">good_result.ftl</result>
115: * </action>
116: * <!-- END SNIPPET: example -->
117: * </pre>
118: *
119: * @author Mike Mosiewicz
120: * @author Rainer Hermanns
121: */
122: public class ScopeInterceptor implements Interceptor, PreResultListener {
123:
124: private static final long serialVersionUID = -8587579255675690482L;
125:
126: private static final Log LOG = LogFactory
127: .getLog(ScopeInterceptor.class);
128:
129: String[] application = null;
130: String[] session = null;
131: String key;
132: String type = null;
133: boolean autoCreateSession = true;
134: String sessionReset = "session.reset";
135: boolean reset = false;
136:
137: //list of application scoped properties
138: public void setApplication(String s) {
139: if (s != null) {
140: application = s.split(" *, *");
141: }
142: }
143:
144: //list of session scoped properties
145: public void setSession(String s) {
146: if (s != null) {
147: session = s.split(" *, *");
148: }
149: }
150:
151: public void setAutoCreateSession(String value) {
152: if (value != null && value.length() > 0) {
153: this .autoCreateSession = new Boolean(value).booleanValue();
154: }
155: }
156:
157: private String getKey(ActionInvocation invocation) {
158: ActionProxy proxy = invocation.getProxy();
159: if (key == null || "CLASS".equals(key)) {
160: return "webwork.ScopeInterceptor:"
161: + proxy.getAction().getClass();
162: } else if ("ACTION".equals(key)) {
163: return "webwork.ScopeInterceptor:" + proxy.getNamespace()
164: + ":" + proxy.getActionName();
165: }
166: return key;
167: }
168:
169: public ScopeInterceptor() {
170: super ();
171: }
172:
173: private static final Object NULL = new Serializable() {
174: public String toString() {
175: return "NULL";
176: }
177: };
178:
179: private static final Object nullConvert(Object o) {
180: if (o == null) {
181: return NULL;
182: }
183:
184: if (o == NULL) {
185: return null;
186: }
187:
188: return o;
189: }
190:
191: private static Map locks = new IdentityHashMap();
192:
193: static final void lock(Object o, ActionInvocation invocation)
194: throws Exception {
195: synchronized (o) {
196: int count = 3;
197: Object previous = null;
198: while ((previous = locks.get(o)) != null) {
199: if (previous == invocation) {
200: return;
201: }
202: if (count-- <= 0) {
203: locks.remove(o);
204: o.notify();
205:
206: throw new WebWorkException(
207: "Deadlock in session lock");
208: }
209: o.wait(10000);
210: }
211: ;
212: locks.put(o, invocation);
213: }
214: }
215:
216: static final void unlock(Object o) {
217: synchronized (o) {
218: locks.remove(o);
219: o.notify();
220: }
221: }
222:
223: protected void after(ActionInvocation invocation, String result)
224: throws Exception {
225: Map ses = ActionContext.getContext().getSession();
226: if (ses != null) {
227: unlock(ses);
228: }
229: }
230:
231: protected void before(ActionInvocation invocation) throws Exception {
232: invocation.addPreResultListener(this );
233: Map ses = ActionContext.getContext().getSession();
234: if (ses == null && autoCreateSession) {
235: ses = new SessionMap(ServletActionContext.getRequest());
236: ActionContext.getContext().setSession(ses);
237: }
238:
239: if (ses != null) {
240: lock(ses, invocation);
241: }
242:
243: String key = getKey(invocation);
244: Map app = ActionContext.getContext().getApplication();
245: final OgnlValueStack stack = ActionContext.getContext()
246: .getValueStack();
247:
248: if (LOG.isDebugEnabled()) {
249: LOG.debug("scope interceptor before");
250: }
251:
252: if (application != null)
253: for (int i = 0; i < application.length; i++) {
254: String string = application[i];
255: Object attribute = app.get(key + string);
256: if (attribute != null) {
257: if (LOG.isDebugEnabled()) {
258: LOG.debug("application scoped variable set "
259: + string + " = "
260: + String.valueOf(attribute));
261: }
262:
263: stack.setValue(string, nullConvert(attribute));
264: }
265: }
266:
267: if (ActionContext.getContext().getParameters()
268: .get(sessionReset) != null) {
269: return;
270: }
271:
272: if (reset) {
273: return;
274: }
275:
276: if (ses == null) {
277: LOG
278: .warn("No HttpSession created... Cannot set session scoped variables");
279: return;
280: }
281:
282: if (session != null && (!"start".equals(type))) {
283: for (int i = 0; i < session.length; i++) {
284: String string = session[i];
285: Object attribute = ses.get(key + string);
286: if (attribute != null) {
287: if (LOG.isDebugEnabled()) {
288: LOG.debug("session scoped variable set "
289: + string + " = "
290: + String.valueOf(attribute));
291: }
292: stack.setValue(string, nullConvert(attribute));
293: }
294: }
295: }
296: }
297:
298: public void setKey(String key) {
299: this .key = key;
300: }
301:
302: public void beforeResult(ActionInvocation invocation,
303: String resultCode) {
304: String key = getKey(invocation);
305: Map app = ActionContext.getContext().getApplication();
306: final OgnlValueStack stack = ActionContext.getContext()
307: .getValueStack();
308:
309: if (application != null)
310: for (int i = 0; i < application.length; i++) {
311: String string = application[i];
312: Object value = stack.findValue(string);
313: if (LOG.isDebugEnabled()) {
314: LOG.debug("application scoped variable saved "
315: + string + " = " + String.valueOf(value));
316: }
317:
318: //if( value != null)
319: app.put(key + string, nullConvert(value));
320: }
321:
322: boolean ends = "end".equals(type);
323:
324: Map ses = ActionContext.getContext().getSession();
325: if (ses != null) {
326:
327: if (session != null) {
328: for (int i = 0; i < session.length; i++) {
329: String string = session[i];
330: if (ends) {
331: ses.remove(key + string);
332: } else {
333: Object value = stack.findValue(string);
334:
335: if (LOG.isDebugEnabled()) {
336: LOG.debug("session scoped variable saved "
337: + string + " = "
338: + String.valueOf(value));
339: }
340:
341: // Null value should be scoped too
342: //if( value != null)
343: ses.put(key + string, nullConvert(value));
344: }
345: }
346: }
347: unlock(ses);
348: } else {
349: LOG
350: .debug("No HttpSession created... Cannot save session scoped variables.");
351: }
352: if (LOG.isDebugEnabled()) {
353: LOG.debug("scope interceptor after (before result)");
354: }
355: }
356:
357: public String getType() {
358: return type;
359: }
360:
361: public void setType(String type) {
362: type = type.toLowerCase();
363: if ("start".equals(type) || "end".equals(type)) {
364: this .type = type;
365: } else {
366: throw new IllegalArgumentException(
367: "Only start or end are allowed arguments for type");
368: }
369: }
370:
371: public String getSessionReset() {
372: return sessionReset;
373: }
374:
375: public void setSessionReset(String sessionReset) {
376: this .sessionReset = sessionReset;
377: }
378:
379: public void destroy() {
380: }
381:
382: public void init() {
383: }
384:
385: public String intercept(ActionInvocation invocation)
386: throws Exception {
387: String result = null;
388: Map ses = ActionContext.getContext().getSession();
389: before(invocation);
390: try {
391: result = invocation.invoke();
392: after(invocation, result);
393: } finally {
394: if (ses != null) {
395: unlock(ses);
396: }
397: }
398:
399: return result;
400: }
401:
402: public boolean isReset() {
403: return reset;
404: }
405:
406: public void setReset(boolean reset) {
407: this.reset = reset;
408: }
409: }
|