Save This Page
Home » struts-2.0.11.2-src » org.apache » struts2 » interceptor » [javadoc | source]
    1   /*
    2    * $Id: ScopeInterceptor.java 520151 2007-03-19 22:48:16Z husted $
    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.struts2.interceptor;
   22   
   23   import java.util.IdentityHashMap;
   24   import java.util.Map;
   25   
   26   import java.io.Serializable;
   27   
   28   import org.apache.commons.logging.Log;
   29   import org.apache.commons.logging.LogFactory;
   30   import org.apache.struts2.ServletActionContext;
   31   import org.apache.struts2.StrutsException;
   32   import org.apache.struts2.dispatcher.SessionMap;
   33   
   34   import com.opensymphony.xwork2.ActionContext;
   35   import com.opensymphony.xwork2.ActionInvocation;
   36   import com.opensymphony.xwork2.ActionProxy;
   37   import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
   38   import com.opensymphony.xwork2.interceptor.PreResultListener;
   39   import com.opensymphony.xwork2.util.ValueStack;
   40   
   41   /**
   42    * <!-- START SNIPPET: description -->
   43    *
   44    * This is designed to solve a few simple issues related to wizard-like functionality in Struts. One of those issues is
   45    * that some applications have a application-wide parameters commonly used, such <i>pageLen</i> (used for records per
   46    * page). Rather than requiring that each action check if such parameters are supplied, this interceptor can look for
   47    * specified parameters and pull them out of the session.
   48    *
   49    * <p/> This works by setting listed properties at action start with values from session/application attributes keyed
   50    * after the action's class, the action's name, or any supplied key. After action is executed all the listed properties
   51    * are taken back and put in session or application context.
   52    *
   53    * <p/> To make sure that each execution of the action is consistent it makes use of session-level locking. This way it
   54    * guarantees that each action execution is atomic at the session level. It doesn't guarantee application level
   55    * consistency however there has yet to be enough reasons to do so. Application level consistency would also be a big
   56    * performance overkill.
   57    *
   58    * <p/> Note that this interceptor takes a snapshot of action properties just before result is presented (using a {@link
   59    * PreResultListener}), rather than after action is invoked. There is a reason for that: At this moment we know that
   60    * action's state is "complete" as it's values may depend on the rest of the stack and specifically - on the values of
   61    * nested interceptors.
   62    *
   63    * <!-- END SNIPPET: description -->
   64    *
   65    * <p/> <u>Interceptor parameters:</u>
   66    *
   67    * <!-- START SNIPPET: parameters -->
   68    *
   69    * <ul>
   70    *
   71    * <li>session - a list of action properties to be bound to session scope</li>
   72    *
   73    * <li>application - a list of action properties to be bound to application scope</li>
   74    *
   75    * <li>key - a session/application attribute key prefix, can contain following values:</li>
   76    *
   77    * <ul>
   78    *
   79    * <li>CLASS - that creates a unique key prefix based on action namespace and action class, it's a default value</li>
   80    *
   81    * <li>ACTION - creates a unique key prefix based on action namespace and action name</li>
   82    *
   83    * <li>any other value is taken literally as key prefix</li>
   84    *
   85    * </ul>
   86    *
   87    * <li>type - with one of the following</li>
   88    *
   89    * <ul>
   90    *
   91    * <li>start - means it's a start action of the wizard-like action sequence and all session scoped properties are reset
   92    * to their defaults</li>
   93    *
   94    * <li>end - means that session scoped properties are removed from session after action is run</li>
   95    *
   96    * <li>any other value or no value means that it's in-the-middle action that is set with session properties before it's
   97    * executed, and it's properties are put back to session after execution</li>
   98    *
   99    * </ul>
  100    *
  101    * <li>sessionReset - boolean value causing all session values to be reset to action's default values or application
  102    * scope values, note that it is similliar to type="start" and in fact it does the same, but in our team it is sometimes
  103    * semantically preferred. We use session scope in two patterns - sometimes there are wizzard-like action sequences that
  104    * have start and end, and sometimes we just want simply reset current session values.</li>
  105    *
  106    * </ul>
  107    *
  108    * <!-- END SNIPPET: parameters -->
  109    *
  110    * <p/> <u>Extending the interceptor:</u>
  111    *
  112    * <p/>
  113    *
  114    * <!-- START SNIPPET: extending -->
  115    *
  116    * There are no know extension points for this interceptor.
  117    *
  118    * <!-- END SNIPPET: extending -->
  119    *
  120    * <p/> <u>Example code:</u>
  121    *
  122    * <pre>
  123    * <!-- START SNIPPET: example -->
  124    * &lt;!-- As the filter and orderBy parameters are common for all my browse-type actions,
  125    *      you can move control to the scope interceptor. In the session parameter you can list
  126    *      action properties that are going to be automatically managed over session. You can
  127    *      do the same for application-scoped variables--&gt;
  128    * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
  129    *     &lt;interceptor-ref name="basicStack"/&gt;
  130    *     &lt;interceptor-ref name="hibernate"/&gt;
  131    *     &lt;interceptor-ref name="scope"&gt;
  132    *         &lt;param name="session"&gt;filter,orderBy&lt;/param&gt;
  133    *         &lt;param name="autoCreateSession"&gt;true&lt;/param&gt;
  134    *     &lt;/interceptor-ref&gt;
  135    *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
  136    * &lt;/action&gt;
  137    * <!-- END SNIPPET: example -->
  138    * </pre>
  139    *
  140    */
  141   public class ScopeInterceptor extends AbstractInterceptor implements PreResultListener {
  142   
  143       private static final long serialVersionUID = 9120762699600054395L;
  144   
  145       private static final Log LOG = LogFactory.getLog(ScopeInterceptor.class);
  146   
  147       private String[] application = null;
  148       private String[] session = null;
  149       private String key;
  150       private String type = null;
  151       private boolean autoCreateSession = true;
  152       private String sessionReset = "session.reset";
  153       private boolean reset = false;
  154   
  155       /**
  156        * Sets a list of application scoped properties
  157        *
  158        * @param s A comma-delimited list
  159        */
  160       public void setApplication(String s) {
  161           if (s != null) {
  162               application = s.split(" *, *");
  163           }
  164       }
  165   
  166       /**
  167        * Sets a list of session scoped properties
  168        *
  169        * @param s A comma-delimited list
  170        */
  171       public void setSession(String s) {
  172           if (s != null) {
  173               session = s.split(" *, *");
  174           }
  175       }
  176   
  177       /**
  178        * Sets if the session should be automatically created
  179        *
  180        * @param value True if it should be created
  181        */
  182       public void setAutoCreateSession(String value) {
  183           if (value != null && value.length() > 0) {
  184               this.autoCreateSession = new Boolean(value).booleanValue();
  185           }
  186       }
  187   
  188       private String getKey(ActionInvocation invocation) {
  189           ActionProxy proxy = invocation.getProxy();
  190           if (key == null || "CLASS".equals(key)) {
  191               return "struts.ScopeInterceptor:" + proxy.getAction().getClass();
  192           } else if ("ACTION".equals(key)) {
  193               return "struts.ScopeInterceptor:" + proxy.getNamespace() + ":" + proxy.getActionName();
  194           }
  195           return key;
  196       }
  197   
  198       /**
  199        * The constructor
  200        */
  201       public ScopeInterceptor() {
  202           super();
  203       }
  204   
  205       // Since 2.0.7. Avoid null references on session serialization (WW-1803).
  206       private static class NULLClass implements Serializable {
  207         public String toString() {
  208           return "NULL";
  209         }
  210         public boolean equals(Object obj) {
  211           return obj == null || (obj instanceof NULLClass);
  212         }
  213       }
  214   
  215       private static final Object NULL = new NULLClass();
  216   
  217       private static final Object nullConvert(Object o) {
  218           if (o == null) {
  219               return NULL;
  220           }
  221   
  222           if (o == NULL || NULL.equals(o)) {
  223               return null;
  224           }
  225   
  226           return o;
  227       }
  228   
  229       private static Map locks = new IdentityHashMap();
  230   
  231       static final void lock(Object o, ActionInvocation invocation) throws Exception {
  232           synchronized (o) {
  233               int count = 3;
  234               Object previous = null;
  235               while ((previous = locks.get(o)) != null) {
  236                   if (previous == invocation) {
  237                       return;
  238                   }
  239                   if (count-- <= 0) {
  240                       locks.remove(o);
  241                       o.notify();
  242   
  243                       throw new StrutsException("Deadlock in session lock");
  244                   }
  245                   o.wait(10000);
  246               }
  247               ;
  248               locks.put(o, invocation);
  249           }
  250       }
  251   
  252       static final void unlock(Object o) {
  253           synchronized (o) {
  254               locks.remove(o);
  255               o.notify();
  256           }
  257       }
  258   
  259       protected void after(ActionInvocation invocation, String result) throws Exception {
  260           Map ses = ActionContext.getContext().getSession();
  261           if ( ses != null) {
  262               unlock(ses);
  263           }
  264       }
  265   
  266   
  267       protected void before(ActionInvocation invocation) throws Exception {
  268           invocation.addPreResultListener(this);
  269           Map ses = ActionContext.getContext().getSession();
  270           if (ses == null && autoCreateSession) {
  271               ses = new SessionMap(ServletActionContext.getRequest());
  272               ActionContext.getContext().setSession(ses);
  273           }
  274   
  275           if ( ses != null) {
  276               lock(ses, invocation);
  277           }
  278   
  279           String key = getKey(invocation);
  280           Map app = ActionContext.getContext().getApplication();
  281           final ValueStack stack = ActionContext.getContext().getValueStack();
  282   
  283           if (LOG.isDebugEnabled()) {
  284               LOG.debug("scope interceptor before");
  285           }
  286   
  287           if (application != null)
  288               for (int i = 0; i < application.length; i++) {
  289                   String string = application[i];
  290                   Object attribute = app.get(key + string);
  291                   if (attribute != null) {
  292                       if (LOG.isDebugEnabled()) {
  293                           LOG.debug("application scoped variable set " + string + " = " + String.valueOf(attribute));
  294                       }
  295   
  296                       stack.setValue(string, nullConvert(attribute));
  297                   }
  298               }
  299   
  300           if (ActionContext.getContext().getParameters().get(sessionReset) != null) {
  301               return;
  302           }
  303   
  304           if (reset) {
  305               return;
  306           }
  307   
  308           if (ses == null) {
  309               LOG.debug("No HttpSession created... Cannot set session scoped variables");
  310               return;
  311           }
  312   
  313           if (session != null && (!"start".equals(type))) {
  314               for (int i = 0; i < session.length; i++) {
  315                   String string = session[i];
  316                   Object attribute = ses.get(key + string);
  317                   if (attribute != null) {
  318                       if (LOG.isDebugEnabled()) {
  319                           LOG.debug("session scoped variable set " + string + " = " + String.valueOf(attribute));
  320                       }
  321                       stack.setValue(string, nullConvert(attribute));
  322                   }
  323               }
  324           }
  325       }
  326   
  327       public void setKey(String key) {
  328           this.key = key;
  329       }
  330   
  331       /* (non-Javadoc)
  332        * @see com.opensymphony.xwork2.interceptor.PreResultListener#beforeResult(com.opensymphony.xwork2.ActionInvocation, java.lang.String)
  333        */
  334       public void beforeResult(ActionInvocation invocation, String resultCode) {
  335           String key = getKey(invocation);
  336           Map app = ActionContext.getContext().getApplication();
  337           final ValueStack stack = ActionContext.getContext().getValueStack();
  338   
  339           if (application != null)
  340               for (int i = 0; i < application.length; i++) {
  341                   String string = application[i];
  342                   Object value = stack.findValue(string);
  343                   if (LOG.isDebugEnabled()) {
  344                       LOG.debug("application scoped variable saved " + string + " = " + String.valueOf(value));
  345                   }
  346   
  347                   //if( value != null)
  348                   app.put(key + string, nullConvert(value));
  349               }
  350   
  351           boolean ends = "end".equals(type);
  352   
  353           Map ses = ActionContext.getContext().getSession();
  354           if (ses != null) {
  355   
  356               if (session != null) {
  357                   for (int i = 0; i < session.length; i++) {
  358                       String string = session[i];
  359                       if (ends) {
  360                           ses.remove(key + string);
  361                       } else {
  362                           Object value = stack.findValue(string);
  363   
  364                           if (LOG.isDebugEnabled()) {
  365                               LOG.debug("session scoped variable saved " + string + " = " + String.valueOf(value));
  366                           }
  367   
  368                           // Null value should be scoped too
  369                           //if( value != null)
  370                           ses.put(key + string, nullConvert(value));
  371                       }
  372                   }
  373               }
  374               unlock(ses);
  375           } else {
  376               LOG.debug("No HttpSession created... Cannot save session scoped variables.");
  377           }
  378           if (LOG.isDebugEnabled()) {
  379               LOG.debug("scope interceptor after (before result)");
  380           }
  381       }
  382   
  383       /**
  384        * @return The type of scope operation, "start" or "end"
  385        */
  386       public String getType() {
  387           return type;
  388       }
  389   
  390       /**
  391        * Sets the type of scope operation
  392        *
  393        * @param type Either "start" or "end"
  394        */
  395       public void setType(String type) {
  396           type = type.toLowerCase();
  397           if ("start".equals(type) || "end".equals(type)) {
  398               this.type = type;
  399           } else {
  400               throw new IllegalArgumentException("Only start or end are allowed arguments for type");
  401           }
  402       }
  403   
  404       /**
  405        * @return Gets the session reset parameter name
  406        */
  407       public String getSessionReset() {
  408           return sessionReset;
  409       }
  410   
  411       /**
  412        * @param sessionReset The session reset parameter name
  413        */
  414       public void setSessionReset(String sessionReset) {
  415           this.sessionReset = sessionReset;
  416       }
  417   
  418       /* (non-Javadoc)
  419        * @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
  420        */
  421       public String intercept(ActionInvocation invocation) throws Exception {
  422           String result = null;
  423           Map ses = ActionContext.getContext().getSession();
  424           before(invocation);
  425           try {
  426               result = invocation.invoke();
  427               after(invocation, result);
  428           } finally {
  429               if (ses != null) {
  430                   unlock(ses);
  431               }
  432           }
  433   
  434           return result;
  435       }
  436   
  437       /**
  438        * @return True if the scope is reset
  439        */
  440       public boolean isReset() {
  441           return reset;
  442       }
  443   
  444       /**
  445        * @param reset True if the scope should be reset
  446        */
  447       public void setReset(boolean reset) {
  448           this.reset = reset;
  449       }
  450   }

Save This Page
Home » struts-2.0.11.2-src » org.apache » struts2 » interceptor » [javadoc | source]