Discussions

J2EE patterns: StackLocal Delegate Pattern

  1. StackLocal Delegate Pattern (5 messages)

    Problem
    When designing systems composed of reusable components there are often situations when one child component is called in multiple contexts and each context has to be treated differently. For example, a customer address and a supplier address may be handled by the same base component but there may be different business logic or security checks depending on the calling context.

    In other cases, local state (i.e. state associated with a method call) must be maintained even after callbacks from third party APIs. I such cases this state currently must be maintained in a ThreadLocal or equiv. since the state cannot be added as a method parameter to the 3rd party APIs.

    Forces
    Designing for calling context often adds addtional methods and/or parameters to each component that are essentially irrelevant to the component's function.

    Coding for calling context also may involve adding logic for each caller to a component which causes code complexity and reduces reusability.

    Java and other languages limit access to the stack for security purposes.

    ThreadLocal is a standard Java API solution for passing local state outside of method parameters but has many risks and problems when used in a thread pooled environment such as a servlet engine.

    Passing local state across callbacks from third pary APIs must involve either ThreadLocal or complex and error-prone static data structures.

    Alternate Solutions
    It is possible to define seperate methods on the address object to handle each calling context but this limits the reusability of the address component since new methods must be added for each new calling context. This works well for small systems but doesn't work as well in large systems with many interactions between components.

    Likewise, it is possible to add a parameter to each method which allows the transmission of the calling context. However, this still could double the number of methods in the address component because cases where the calling context is irrelevant should be handled (such as when the address component is called independently). Also, the context parameter is unavailable after callbacks from 3rd party APIs (where you have little or no control over the method signatures). An simple example of this the the way Locale is explicitly passed in HttpServletRequest and must in turn be passed explicitly to all called APIs to produce localized results. APIs which have no access to HttpServletRequest have no other way to access the current request's Locale.

    The Java ThreadLocal class provides a partial but unsatisfactory solution to these issues. While it allows a calling context without requiring new methods, ThreadLocal cannot provide an automated cleanup facility or prevent possible side effects. For instance, in a servlet engine which utilizes a thread pool, ThreadLocal content will not be garbage collected until the Thread is garbage collected (which may be never). Even worse, if the TreadLocal is not re-initialized properly between HTTP requests another unrelated request may receive an invalid value left over from a previous request.

    Solution
    Use a StackLocal Delegate to set and retrive local state and encapsulate functionality which is common to most components but not directly related to the current component's functionality.

    Structure

    (Please excuse my ASCII UML diagram...)

       ------------- uses ----------------------- encapsulates --------------
       | Component |----------| StackLocal Delegate |----------------| StackLocal |
       ------------- ----------------------- --------------

    Participants and Responabilities

    Component: Represents not just a single component, but any number of arbitrary method calls on any number of contained or containing components in any order.

    StackLocal Delegate: Contains functionality which is common between components and requires access to StackLocal state.

    StackLocal: Saves state which is local to the current call stack. i.e. when the current method call returns, the state from the previous call is restored. Also, when the current call sets state it is available to all calls under the current call in the call stack.

    Consequences

    + Crosscut concerns due to calling context are reduced. This reduces the size and complexity of the component code and also increases modularity and maintainability.

    + Common logic in the StackLocal Delegate is reuseable for new Components.

    + The StackLocal Delegate protects the Components from changes to the implementation of the StackLocal.

    + New Components can be added and can use common components without any changes to the common components.

    + Component method calls can be more easily combined in novel ways, even with intervening 3rd party API callbacks.

    + A StackLoca Delegate is type safe while a threadLocal is not.

    - The inner workings of StackLocal are mystifying to developers not familliar with ThreadLocal or call stacks.

    - Implementation of StackLocal can not be very fast or memory efficient unless it is supported at the JVM level. The current implementation is tolerable but not optimal.

    - Passing StackLocal state across virtual machine boundries can be complex since it is not passed as a method parameter. The provided implementation below does not support this, but Interceptor can be used for this purpose if necessary.

    StackLocal Implementation
    A working implementation of a Java StackLocal is available upon request. I will also try to post it as a RE: to this post

    Examples

    (Again, please excuse the ASCII UML...)

        ------------ ------------
        | Supplier | | Customer |
        ------------ ------------
              | / |
              | / |
              |n / n |n
              | / |
          ------------ n -----------
          | Address |------| Contact |
          ------------ -----------

    In our customer and supplier example above we could create a StackLocal Delegate called Authorizer:

    /** Stubbed out class to demonstrate interface of Authorizer. Please see StackLocalTest.class above
     * for a real working demo of a StackLocal. */
    public class Authorizer {

      /** Initializes the context, please see StackLocal implementation example code above for details.
       * StackLocal variables, like ThreadLocals, should only be defined as static */
      private static final StackLocal context = new StackLocal(Authorizer.class);

      /** Gets the stack local context */
      public static String getContext() {
        return (String)context.get();
      }

      /** Sets the context for the canRead() checks */
      public static void setContext(String ctx) {
        String current = (String)context.get();
        if(current == null) {
          context.set('/' + ctx);
        } else {
          context.set(current + '/' + ctx);
        }
      }

      /** Checks if the user can read in the current context, if not throw SecurityException.
       * Example: if Address is the current context, this method should check permission
       * read/Address and throw SecurityException on fail. If /Customer/Address is the current
       * context then this method should check permission read/Address and permission
       * read/Customer/Address and if both checks fail then throw SecurityException */
      public static void canRead() throws SecurityException {
        String context = getContext();
        String permission = "read" + context.substring(context.lastIndexOf('/'));
        //Code to check other permissions here
      }

    }

    Please assume for this example that information on the current user (such as userid, roles, and permissions) is available elsewhere (such as through JAAS). The logic to access this information is also contained in the Authorizer class but not described here.

    Users can be given read permissions on a single entity, such as "read/Supplier", or arbitrary nested entities, such as "read/Customer/Contact/Address". Note that for security purposes a customer address and a supplier address are very different things even though they may be managed by the same Address component.

    When the Address component receives a request for an Address the first thing it does is call Authorizer.setContext("Address") and Authorizer.canRead(). In this case, Authorizer checks the permission "read/Address" and throws a SecurityException if this check fails.

    This is all the code that the Address component requires to support any possible caller. Here's why:

    If the user accesses the Address component in the context of the Supplier component (these could be DAOs, DTOs, EntityBeans, etc.). Let's use the simplest case of a DAO. The SupplierDAO has a method to get all the Addresses for a Supplier. This method first calls Authorizer.setContext("Supplier") and Authorizer.canRead(), then calls the AddressDAO to get the Addresses. The AddressDAO calls Authorizer.setContext("Address") and then Authorizer.canRead(). The current context is now set to "/Supplier/Address" and the Authorizer first checks "read/Address" (permission to read all addresses) then "read/Supplier/Address" and if both fail throws a SecurityException.
  2. Sample code for StackLocal[ Go to top ]

    /*
     * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
     * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL MATT POUTTU-CLARKE OR HIS EMPLOYERS
     * OR INTERMEDIARIES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
     * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     * SUCH DAMAGE. */
    import java.util.*;

    /**
     * Provides a stack local variable. This is much like a ThreadLocal except that
     * it's value is only valid for the current class call stack. After the value is
     * set, it is accessible from the current method and all methods which the
     * current method calls. Once the current method returns then the value reverts
     * to the previous value set at a higher level in the call stack or null if there
     * was no value set at a higher level in the call stack.
     * @author mpouttu-clarke
     */
    public class StackLocal extends ThreadLocal {
        
        /** Inner class to provide a call stack */
        private static class CallStack extends SecurityManager {
            private static final CallStack instance = new CallStack();
            
            public static Class[] getClassCallStack() {
                return instance.getClassContext();
            }
        };
        
        Class ignoreClass = null;
        
        /** Holds value of property initialValue. */
        private Object initialValue;
        
        /** Creates a new instance of StackLocal */
        public StackLocal() {
            super();
        }
        
        /** Creates a new instance of StackLocal with a class to ignore in the call
         * stack. The class to ignore would usually be a wrapper which desires to
         * be transparent to the call stack.
         */
        public StackLocal(Class ignoreClass) {
            super();
            this.ignoreClass = ignoreClass;
        }
            
        /** Sets the value which is always the default regardless of
         * the call stack invloved. This also has the effect of resetting all
         * previously set values.
         * @param initialValue The new value to set
         */
        public void setInitialValue(Object initialValue) {
            if(initialValue == null) {
                super.set(null); //Reset all
            } else {
                StackLocalValue wrapper = new StackLocalValue();
                wrapper.setValue(initialValue);
                wrapper.setStack(new String[0]);
                List list = new ArrayList();
                list.add(wrapper);
                super.set(list);
            }
        }
        
        public String[] getCallStack() {
            Class[] all = CallStack.getClassCallStack();
            int ignore = 1; //Always ignore UserContextManager
            for(int x = 0; x < all.length; x++) {
                if(StackLocal.class.isAssignableFrom(all[x])) {
                    ignore++; //Ignore yourself and your subclasses because the user doesn't care
                } else if(ignoreClass != null
                       && ignoreClass.isAssignableFrom(all[x])) {
                    ignore++; //Ignore the class and subclasses the user wants you to if applicable
                }
            }
            String[] sub = new String[all.length - ignore + 1];
            int x = 0;
            for(int y = ignore; y < all.length; y++) {
                sub[x++] = all[y].getName();
            }
            sub[sub.length - 1] = Thread.currentThread().getName();
            return sub;
        }
        
        public static boolean isStackAtOrUnder(String[] stack, String[] stackTest) {
            if(stackTest.length < stack.length) {
                return false;
            }
            int diff = stackTest.length - stack.length;
            for(int x = 0; x < stack.length ; x++) {
                if(!stack[x].equals(stackTest[x + diff])) {
                    return false;
                }
            }
            return true;
        }
        
        public static boolean isStackPartOf(String[] stack, String[] stackTest) {
            if(stackTest.length > stack.length) {
                return false;
            }
            int diff = stack.length - stackTest.length;
            int count = 0;
            for(int x = 0; x < stackTest.length; x++) {
                if(stackTest[x].equals(stack[x + diff])) {
                    count++;
                }
            }
            return count == stackTest.length;
        }
        
        public Object get() {
            StackLocalValue value = getStackLocalValue();
            if(value == null) {
                return null;
            }
            return value.getValue();
        }

        protected StackLocalValue getStackLocalValue() {
            List result = (List)super.get();
            if(result == null) {
                return null;
            }
            String[] stack = getCallStack();
            
            StackLocalValue value = null;
           
            //Clean out all values which are invalid
            for(Iterator i = result.iterator(); i.hasNext();) {
                value = (StackLocalValue)i.next();
                if(!isStackPartOf(stack, value.getStack())) {
                    i.remove();
                }
            }
            
            //Get the value if there is one left
            for(int x = result.size() - 1; x > -1; x--) {
                value = (StackLocalValue)result.get(x);
                if(isStackAtOrUnder(value.getStack(), stack)) {
                    return value;
                }
            }
            
            //If there was no valid value then reset the super value
            super.set(null);
            return null;
        }
        
        public void set(Object value) {
            StackLocalValue wrapper = getStackLocalValue();
            String[] stack = getCallStack();
            if(wrapper == null) { //New stack value
                List list = (List)super.get();
                if(list == null) {
                    list = new ArrayList();
                }
                wrapper = new StackLocalValue();
                wrapper.setStack(stack);
                wrapper.setValue(value);
                list.add(wrapper);
                super.set(list);
            } else if(Arrays.equals(wrapper.getStack(), stack)) { //Same stack value
                wrapper.setValue(value);
            } else { //Sub stack value
                List list = (List)super.get();
                wrapper = new StackLocalValue();
                wrapper.setStack(stack);
                wrapper.setValue(value);
                list.add(wrapper);
                super.set(list);
            }
        }
        
        public Object[] getAllValues() {
            List result = (List)super.get();
            if(result == null) {
                return new Object[0];
            }
            String[] stack = getCallStack();
            
            StackLocalValue value = null;
           
            //Clean out all values which are invalid
            for(Iterator i = result.iterator(); i.hasNext();) {
                value = (StackLocalValue)i.next();
                if(!isStackPartOf(stack, value.getStack())) {
                    i.remove();
                }
            }
            
            List resultList = new ArrayList(result.size());
            //Get the values if there is one left
            for(int x = 0; x < result.size(); x++) {
                value = (StackLocalValue)result.get(x);
                if(isStackAtOrUnder(value.getStack(), stack)) {
                    resultList.add(value.getValue());
                }
            }
            
            if(resultList.size() < 1) {
                //If there was no valid values then reset the super value
                super.set(null);
                return new Object[0];
            }
            
            return resultList.toArray();
        }
        
        private class StackLocalValue {
            
            /** Holds value of property value. */
            private Object value;
            
            /** Holds value of property stack. */
            private String[] stack;
            
            /** Creates a new instance of StackLocalValue */
            public StackLocalValue() {
            }
            
            /** Getter for property value.
             * @return Value of property value.
             */
            public Object getValue() {
                return this.value;
            }
            
            /** Setter for property value.
             * @param value New value of property value.
             */
            public void setValue(Object value) {
                this.value = value;
            }
            
            /** Getter for property stack.
             * @return Value of property stack.
             */
            public String[] getStack() {
                return this.stack;
            }
            
            /** Setter for property stack.
             * @param stack New value of property stack.
             */
            public void setStack(String[] stack) {
                this.stack = stack;
            }
            
        }
        
        public static void main(String[] args) {
            if(isStackAtOrUnder(new String[] { "6","5","4","3" }, new String[] { "3" })) {
                throw new RuntimeException("Fail");
            }
            if(isStackAtOrUnder(new String[] { "6","5","4","3" }, new String[] { "7","5","4","3" })) {
                throw new RuntimeException("Fail");
            }
            if(isStackAtOrUnder(new String[] { "6","5","4","3" }, new String[] { "7","6","4","4","3" })) {
                throw new RuntimeException("Fail");
            }
            if(!isStackAtOrUnder(new String[0], new String[] { "3" })) {
                throw new RuntimeException("Fail");
            }
            if(!isStackAtOrUnder(new String[] { "6","5","4","3" }, new String[] { "7","6","5","4","3"})) {
                throw new RuntimeException("Fail");
            }
            if(!isStackAtOrUnder(new String[] { "6","5","4","3" }, new String[] { "6","5","4","3" })) {
                throw new RuntimeException("Fail");
            }
            
            if(isStackPartOf(new String[0], new String[] { "2" })) {
                throw new RuntimeException("Fail");
            }
            if(isStackPartOf(new String[] { "6","5","4","3" }, new String[] { "2" })) {
                throw new RuntimeException("Fail");
            }
            if(isStackPartOf(new String[] { "6","5","4","3" }, new String[] { "6","5","4","3","2" })) {
                throw new RuntimeException("Fail");
            }
            if(!isStackPartOf(new String[] { "6","5","4","3" }, new String[] { "3" })) {
                throw new RuntimeException("Fail");
            }
            if(!isStackPartOf(new String[] { "6","5","4","3" }, new String[] { "6","5","4","3" })) {
                throw new RuntimeException("Fail");
            }
            System.out.println("Success");
        }
        
    }
  3. /*
     * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
     * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL MATT POUTTU-CLARKE OR HIS EMPLOYERS
     * OR INTERMEDIARIES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
     * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     * SUCH DAMAGE. */

    import junit.framework.*;
    import java.util.*;

    /**
     * Test for StackLocal
     * @author mpouttu-clarke
     */
    public class StackLocalTest extends TestCase {
        
        private static StackLocal stackLocal = new StackLocal();
        
        public StackLocalTest(java.lang.String testName) {
            super(testName);
        }
        
        public static void main(java.lang.String[] args) {
            junit.textui.TestRunner.run(suite());
        }
        
        public static Test suite() {
            TestSuite suite = new TestSuite(StackLocalTest.class);
            return suite;
        }

        public void sub(int iteration) {
            if(iteration > 5) {
                return;
            }
            iteration++;
            System.out.println("before set iteration " + iteration + " " + stackLocal.get());
            stackLocal.set(" " + stackLocal.get() + " " + iteration);
            System.out.println("before sub iteration " + iteration + " " + stackLocal.get() + " all " + Arrays.asList(stackLocal.getAllValues()));
            sub(iteration);
            System.out.println("after sub iteration " + iteration + " " + stackLocal.get() + " all " + Arrays.asList(stackLocal.getAllValues()));
        }
        
        public void root() {
            stackLocal.setInitialValue("root");
        }
        
        public void test() {
            root();
            System.out.println(stackLocal.get());
            sub(0);
            System.out.println(stackLocal.get());
            stackLocal.setInitialValue(null);
            System.out.println(stackLocal.get());
        }
        
    }
  4. Hi Matt,

    We're thinking about using a ThreadLocal to store context information that needs to be accessed from objects running in either a servlet container or an EJB container. Naturally, I got concerned by your statement

    "ThreadLocal is a standard Java API solution for passing local state outside of method parameters but has many risks and problems when used in a thread pooled environment such as a servlet engine."

    Could you elaborate on that? What we're specifically interested in is: If the ThreadLocal is initialized upon handling each request / EJB-method call and set to null after all work is done, would there still be any risks and problems remaining?


    Thankyou very much!
    Thomas
  5. Hi Matt,

    >
    > We're thinking about using a ThreadLocal to store context information that needs to be accessed from objects running in either a servlet container or an EJB container. Naturally, I got concerned by your statement
    >
    > "ThreadLocal is a standard Java API solution for passing local state outside of method parameters but has many risks and problems when used in a thread pooled environment such as a servlet engine."
    >
    > Could you elaborate on that? What we're specifically interested in is: If the ThreadLocal is initialized upon handling each request / EJB-method call and set to null after all work is done, would there still be any risks and problems remaining?
    >
    >
    > Thankyou very much!
    > Thomas
  6. Hi Matt,

    >
    > We're thinking about using a ThreadLocal to store context information that needs to be accessed from objects running in either a servlet container or an EJB container. Naturally, I got concerned by your statement
    >
    > "ThreadLocal is a standard Java API solution for passing local state outside of method parameters but has many risks and problems when used in a thread pooled environment such as a servlet engine."
    >
    > Could you elaborate on that? What we're specifically interested in is: If the ThreadLocal is initialized upon handling each request / EJB-method call and set to null after all work is done, would there still be any risks and problems remaining?
    >
    >
    > Thankyou very much!
    > Thomas

    There are two problems, a minor one and a major one:
    1. You need to absolutely garantee that the ThreadLocal initialized and freed every time using custom code. That means catching Throwable (NOT Exception) or using finally as well as making sure all entry points reset the ThreadLocal correctly. If you do not do this perfectly then you will introduce a bug to your program that will be very hard to track down and fix.

    2. ThreadLocals do not prevent side effects the way method parameters do. i.e. a ThreadLocal object reference can be reassigned to another object by called methods without calling method's knowledge. This a security risk as well as just plain bad design. If you pass a String to a method even if the method assigns the String to some other String when your method call returns the String you passed still has the same value. If you pass the String using a ThreadLocal then the called method can change the value to another String and the value is reflected after the called method returns. This kind of issue is also very difficult to find and fix.

    These issues are the key things the StackLocal prevents. However, please note that the StackLocal code is provided as is with no warranty of any kind.

    Regards,
    Matt