001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017     
018    package org.apache.commons.beanutils;
019    
020    import java.util.Map;
021    import java.util.WeakHashMap;
022    
023    /**
024     * An instance of this class represents a value that is provided per (thread)
025     * context classloader.
026     * 
027     * <p>Occasionally it is necessary to store data in "global" variables
028     * (including uses of the Singleton pattern). In applications which have only
029     * a single classloader such data can simply be stored as "static" members on
030     * some class. When multiple classloaders are involved, however, this approach
031     * can fail; in particular, this doesn't work when the code may be run within a
032     * servlet container or a j2ee container, and the class on which the static
033     * member is defined is loaded via a "shared" classloader that is visible to all
034     * components running within the container. This class provides a mechanism for
035     * associating data with a ClassLoader instance, which ensures that when the
036     * code runs in such a container each component gets its own copy of the
037     * "global" variable rather than unexpectedly sharing a single copy of the
038     * variable with other components that happen to be running in the same
039     * container at the same time (eg servlets or EJBs.)</p>
040     *
041     * <p>This class is strongly patterned after the java.lang.ThreadLocal
042     * class, which performs a similar task in allowing data to be associated
043     * with a particular thread.</p>
044     *
045     * <p>When code that uses this class is run as a "normal" application, ie
046     * not within a container, the effect is identical to just using a static 
047     * member variable to store the data, because Thread.getContextClassLoader
048     * always returns the same classloader (the system classloader).</p>
049     *
050     * <p>Expected usage is as follows:<br>
051     * <pre>
052     *  public class SomeClass {
053     *    private static final ContextClassLoaderLocal global 
054     *      = new ContextClassLoaderLocal() {
055     *          protected Object initialValue() {
056     *              return new String("Initial value");
057     *          };
058     *
059     *    public void testGlobal() {
060     *      String s = (String) global.get();
061     *      System.out.println("global value:" + s);
062     *      buf.set("New Value");
063     *    }
064     * </pre>
065     * </p>
066     *
067     * <p><strong>Note:</strong> This class takes some care to ensure that when
068     * a component which uses this class is "undeployed" by a container the
069     * component-specific classloader and all its associated classes (and their
070     * static variables) are garbage-collected. Unfortunately there is one
071     * scenario in which this does <i>not</i> work correctly and there
072     * is unfortunately no known workaround other than ensuring that the
073     * component (or its container) calls the "unset" method on this class for
074     * each instance of this class when the component is undeployed. The problem
075     * occurs if:
076     * <ul>
077     * <li>the class containing a static instance of this class was loaded via
078     * a shared classloader, and</li>
079     * <li>the value stored in the instance is an object whose class was loaded
080     * via the component-specific classloader (or any of the objects it refers
081     * to were loaded via that classloader).</li>
082     * </ul>
083     * The result is that the map managed by this object still contains a strong
084     * reference to the stored object, which contains a strong reference to the
085     * classloader that loaded it, meaning that although the container has
086     * "undeployed" the component the component-specific classloader and all the
087     * related classes and static variables cannot be garbage-collected. This is
088     * not expected to be an issue with the commons-beanutils library as the only
089     * classes which use this class are BeanUtilsBean and ConvertUtilsBean and
090     * there is no obvious reason for a user of the beanutils library to subclass
091     * either of those classes.</p>
092     *
093     * <p><strong>Note:</strong> A WeakHashMap bug in several 1.3 JVMs results in 
094     * a memory leak for those JVMs.</p>
095     *
096     * <p><strong>Note:</strong> Of course all of this would be unnecessary if
097     * containers required each component to load the full set of classes it
098     * needs, ie avoided providing classes loaded via a "shared" classloader.</p>
099     * 
100     * @see java.lang.Thread#getContextClassLoader  
101     * @author Eric Pabst
102     */
103    public class ContextClassLoaderLocal {
104        private Map valueByClassLoader = new WeakHashMap();
105        private boolean globalValueInitialized = false;
106        private Object globalValue;
107    
108        /**
109         * Construct a context classloader instance
110         */
111        public ContextClassLoaderLocal() {
112            super();
113        }
114    
115        /**
116         * Returns the initial value for this ContextClassLoaderLocal
117         * variable. This method will be called once per Context ClassLoader for
118         * each ContextClassLoaderLocal, the first time it is accessed 
119         * with get or set.  If the programmer desires ContextClassLoaderLocal variables
120         * to be initialized to some value other than null, ContextClassLoaderLocal must
121         * be subclassed, and this method overridden.  Typically, an anonymous
122         * inner class will be used.  Typical implementations of initialValue
123         * will call an appropriate constructor and return the newly constructed
124         * object.
125         *
126         * @return a new Object to be used as an initial value for this ContextClassLoaderLocal
127         */
128        protected Object initialValue() {
129            return null;
130        }
131    
132        /** 
133         * Gets the instance which provides the functionality for {@link BeanUtils}.
134         * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
135         * This mechanism provides isolation for web apps deployed in the same container. 
136         * @return the object currently associated with the context-classloader of the current thread. 
137         */
138        public synchronized Object get() {
139            // synchronizing the whole method is a bit slower 
140            // but guarantees no subtle threading problems, and there's no 
141            // need to synchronize valueByClassLoader
142            
143            // make sure that the map is given a change to purge itself
144            valueByClassLoader.isEmpty();
145            try {
146                
147                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
148                if (contextClassLoader != null) {
149                    
150                    Object value = valueByClassLoader.get(contextClassLoader);
151                    if ((value == null) 
152                    && !valueByClassLoader.containsKey(contextClassLoader)) {
153                        value = initialValue();
154                        valueByClassLoader.put(contextClassLoader, value);
155                    }
156                    return value;
157                    
158                }
159                
160            } catch (SecurityException e) { /* SWALLOW - should we log this? */ }
161            
162            // if none or exception, return the globalValue 
163            if (!globalValueInitialized) {
164                globalValue = initialValue();
165                globalValueInitialized = true;
166            }//else already set
167            return globalValue;
168        }
169    
170        /** 
171         * Sets the value - a value is provided per (thread) context classloader.
172         * This mechanism provides isolation for web apps deployed in the same container. 
173         * 
174         * @param value the object to be associated with the entrant thread's context classloader
175         */
176        public synchronized void set(Object value) {
177            // synchronizing the whole method is a bit slower 
178            // but guarentees no subtle threading problems
179            
180            // make sure that the map is given a change to purge itself
181            valueByClassLoader.isEmpty();
182            try {
183                
184                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
185                if (contextClassLoader != null) {
186                    valueByClassLoader.put(contextClassLoader, value);
187                    return;
188                }
189                
190            } catch (SecurityException e) { /* SWALLOW - should we log this? */ }
191            
192            // if in doubt, set the global value
193            globalValue = value;
194            globalValueInitialized = true;
195        }
196        
197        /** 
198         * Unsets the value associated with the current thread's context classloader
199         */
200        public synchronized void unset() {    
201            try {
202            
203                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
204                unset(contextClassLoader);
205                
206            } catch (SecurityException e) { /* SWALLOW - should we log this? */ }
207        }
208        
209        /** 
210         * Unsets the value associated with the given classloader
211         * @param classLoader The classloader to <i>unset</i> for
212         */
213        public synchronized void unset(ClassLoader classLoader) {    
214            valueByClassLoader.remove(classLoader);
215        }    
216    }