KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > appserv > ClassLoaderUtil


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23
24 package com.sun.appserv;
25
26 import java.io.IOException JavaDoc;
27 import java.lang.reflect.Field JavaDoc;
28 import java.net.URLClassLoader JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.Stack JavaDoc;
32 import java.util.Vector JavaDoc;
33 import java.util.jar.JarFile JavaDoc;
34 import java.util.logging.Formatter JavaDoc;
35 import java.util.logging.Level JavaDoc;
36 import java.util.logging.Logger JavaDoc;
37 import java.text.MessageFormat JavaDoc;
38 import sun.misc.URLClassPath;
39 import com.sun.common.util.logging.LogDomains;
40
41
42 /**
43  * Provides utility functions related to URLClassLoaders or subclasses of it.
44  *
45  * W A R N I N G
46  *
47  *This class uses undocumented, unpublished, private data structures inside
48  *java.net.URLClassLoader and sun.misc.URLClassPath. Use with extreme caution.
49  *
50  * @author tjquinn
51  */

52 public class ClassLoaderUtil {
53
54     /** records whether initialization has been completed */
55     private static boolean isInitialized = false;
56     
57     /** names of classes and fields of interest for closing the loader's jar files */
58     private static final String JavaDoc URLCLASSLOADER_UCP_FIELD_NAME = "ucp";
59     
60     private static final String JavaDoc URLCLASSPATH_LOADERS_FIELD_NAME = "loaders"; // ArrayList of URLClassPath.Loader
61
private static final String JavaDoc URLCLASSPATH_URLS_FIELD_NAME = "urls"; // Stack of URL
62
private static final String JavaDoc URLCLASSPATH_LMAP_FIELD_NAME = "lmap"; // HashMap of String -> URLClassPath.Loader
63

64     private static final String JavaDoc URLCLASSPATH_JARLOADER_INNER_CLASS_NAME = "sun.misc.URLClassPath$JarLoader";
65     private static final String JavaDoc URLCLASSPATH_JARLOADER_JARFILE_FIELD_NAME = "jar";
66     
67     /* Fields used during processing - they can be set up once and then used repeatedly */
68     private static Field JavaDoc jcpField;
69     private static Field JavaDoc loadersField;
70     private static Field JavaDoc urlsField;
71     private static Field JavaDoc lmapField;
72     private static Class JavaDoc jarLoaderInnerClass;
73
74     private static Field JavaDoc jarFileField;
75     
76     private static boolean initDone = false;
77     
78     /**
79      *Initializes the class.
80      *<p>
81      *Each utility method should invoke init() before doing their own work
82      *to make sure the initialization is done.
83      *@throws any Throwable detected during static init.
84      */

85     private static void init() throws Throwable JavaDoc {
86         if ( ! initDone) {
87             initForClosingJars();
88             initDone = true;
89         }
90     }
91     
92     /**
93      *Sets up variables used in closing a loader's jar files.
94      *@throws NoSuchFieldException in case a field of interest is not found where expected
95      */

96     private static void initForClosingJars() throws NoSuchFieldException JavaDoc {
97         jcpField = getField(URLClassLoader JavaDoc.class, URLCLASSLOADER_UCP_FIELD_NAME);
98         loadersField = getField(URLClassPath.class, URLCLASSPATH_LOADERS_FIELD_NAME);
99         urlsField = getField(URLClassPath.class, URLCLASSPATH_URLS_FIELD_NAME);
100         lmapField = getField(URLClassPath.class, URLCLASSPATH_LMAP_FIELD_NAME);
101         
102         jarLoaderInnerClass = getInnerClass(URLClassPath.class, URLCLASSPATH_JARLOADER_INNER_CLASS_NAME);
103         jarFileField = getField(jarLoaderInnerClass, URLCLASSPATH_JARLOADER_JARFILE_FIELD_NAME);
104     }
105     
106     /**
107      *Retrieves a Field object for a given field on the specified class, having
108      *set it accessible.
109      *@param cls the Class on which the field is expected to be defined
110      *@param fieldName the name of the field of interest
111      *@throws NoSuchFieldException in case of any error retriving information about the field
112      */

113     private static Field JavaDoc getField(Class JavaDoc cls, String JavaDoc fieldName) throws NoSuchFieldException JavaDoc {
114         try {
115             Field JavaDoc field = cls.getDeclaredField(fieldName);
116             field.setAccessible(true);
117             return field;
118         } catch (NoSuchFieldException JavaDoc nsfe) {
119             NoSuchFieldException JavaDoc e = new NoSuchFieldException JavaDoc(getMessage("classloaderutil.errorGettingField", fieldName));
120             e.initCause(nsfe);
121             throw e;
122         }
123
124     }
125     
126     /**
127      *Retrieves a given inner class definition from the specified outer class.
128      *@param cls the outer Class
129      *@param innerClassName the fully-qualified name of the inner class of interest
130      */

131     private static Class JavaDoc getInnerClass(Class JavaDoc cls, String JavaDoc innerClassName) {
132         Class JavaDoc result = null;
133         Class JavaDoc[] innerClasses = cls.getDeclaredClasses();
134         for (Class JavaDoc c : innerClasses) {
135             if (c.getName().equals(innerClassName)) {
136                 result = c;
137                 break;
138             }
139         }
140         return result;
141     }
142     
143     /**
144      *Releases resources held by the URLClassLoader. Notably, close the jars
145      *opened by the loader. This does not prevent the class loader from
146      *continuing to return classes it has already resolved.
147      *@param classLoader the instance of URLClassLoader (or a subclass)
148      *@return array of IOExceptions reporting jars that failed to close
149      */

150     public static void releaseLoader(URLClassLoader JavaDoc classLoader) {
151         releaseLoader(classLoader, null);
152     }
153     
154     /**
155      *Releases resources held by the URLClassLoader. Notably, close the jars
156      *opened by the loader. This does not prevent the class loader from
157      *continuing to return classes it has alrady resolved although that is not
158      *what we intend to happen. Initializes and updates the Vector of
159      *jars that have been successfully closed.
160      *<p>
161      *Any errors are logged.
162      *@param classLoader the instance of URLClassLoader (or a subclass)
163      *@param jarsClosed a Vector of Strings that will contain the names of jars
164      * successfully closed; can be null if the caller does not need the information returned
165      *@return array of IOExceptions reporting jars that failed to close; null
166      *indicates that an error other than an IOException occurred attempting to
167      *release the loader; empty indicates a successful release; non-empty
168      *indicates at least one error attempting to close an open jar.
169      */

170     public static IOException JavaDoc [] releaseLoader(URLClassLoader JavaDoc classLoader, Vector JavaDoc<String JavaDoc> jarsClosed) {
171         
172         IOException JavaDoc[] result = null;
173         
174         try {
175             init();
176
177             /* Records all IOExceptions thrown while closing jar files. */
178             Vector JavaDoc<IOException JavaDoc> ioExceptions = new Vector JavaDoc<IOException JavaDoc>();
179
180             if (jarsClosed != null) {
181                 jarsClosed.clear();
182             }
183
184             URLClassPath ucp = (URLClassPath) jcpField.get(classLoader);
185             ArrayList JavaDoc loaders = (ArrayList JavaDoc) loadersField.get(ucp);
186             Stack JavaDoc urls = (Stack JavaDoc) urlsField.get(ucp);
187             HashMap JavaDoc lmap = (HashMap JavaDoc) lmapField.get(ucp);
188
189             /*
190              *The urls variable in the URLClassPath object holds URLs that have not yet
191              *been used to resolve a resource or load a class and, therefore, do
192              *not yet have a loader associated with them. Clear the stack so any
193              *future requests that might incorrectly reach the loader cannot be
194              *resolved and cannot open a jar file after we think we've closed
195              *them all.
196              */

197             synchronized(urls) {
198                 urls.clear();
199             }
200
201             /*
202              *Also clear the map of URLs to loaders so the class loader cannot use
203              *previously-opened jar files - they are about to be closed.
204              */

205             synchronized(lmap) {
206                 lmap.clear();
207             }
208
209             /*
210              *The URLClassPath object's path variable records the list of all URLs that are on
211              *the URLClassPath's class path. Leave that unchanged. This might
212              *help someone trying to debug why a released class loader is still used.
213              *Because the stack and lmap are now clear, code that incorrectly uses a
214              *the released class loader will trigger an exception if the
215              *class or resource would have been resolved by the class
216              *loader (and no other) if it had not been released.
217              *
218              *The list of URLs might provide some hints to the person as to where
219              *in the code the class loader was set up, which might in turn suggest
220              *where in the code the class loader needs to stop being used.
221              *The URLClassPath does not use the path variable to open new jar
222              *files - it uses the urls Stack for that - so leaving the path variable
223              *will not by itself allow the class loader to continue handling requests.
224              */

225
226             /*
227              *For each loader, close the jar file associated with that loader.
228              *
229              *The URLClassPath's use of loaders is sync-ed on the entire URLClassPath
230              *object.
231              */

232             synchronized (ucp) {
233                 for (Object JavaDoc o : loaders) {
234                     if (o != null) {
235                         /*
236                          *If the loader is a JarLoader inner class and its jarFile
237                          *field is non-null then try to close that jar file. Add
238                          *it to the list of closed files if successful.
239                          */

240                         if (jarLoaderInnerClass.isInstance(o)) {
241                             try {
242                                 JarFile JavaDoc jarFile = (JarFile JavaDoc) jarFileField.get(o);
243                                 try {
244                                     if (jarFile != null) {
245                                         jarFile.close();
246                                         if (jarsClosed != null) {
247                                             jarsClosed.add(jarFile.getName());
248                                         }
249                                     }
250                                 } catch (IOException JavaDoc ioe) {
251                                     /*
252                                      *Wrap the IOException to identify which jar
253                                      *could not be closed and add it to the list
254                                      *of IOExceptions to be returned to the caller.
255                                      */

256                                     String JavaDoc jarFileName = (jarFile == null) ? getMessage("classloaderutil.jarFileNameNotAvailable") : jarFile.getName();
257                                     String JavaDoc msg = getMessage("classloaderutil.errorClosingJar", jarFileName);
258                                     IOException JavaDoc newIOE = new IOException JavaDoc(msg);
259                                     newIOE.initCause(ioe);
260                                     ioExceptions.add(newIOE);
261                                     
262                                     /*
263                                      *Log the error also.
264                                      */

265                                     getLogger().log(Level.WARNING, msg, ioe);
266                                 }
267                             } catch (Throwable JavaDoc thr) {
268                                 getLogger().log(Level.WARNING, "classloaderutil.errorReleasingJarNoName", thr);
269                             }
270                         }
271                     }
272                 }
273                 /*
274                  *Now clear the loaders ArrayList.
275                  */

276                 loaders.clear();
277             }
278             result = ioExceptions.toArray(new IOException JavaDoc[ioExceptions.size()]);
279         } catch (Throwable JavaDoc thr) {
280             getLogger().log(Level.WARNING, "classloaderutil.errorReleasingLoader", thr);
281             result = null;
282         }
283         
284         return result;
285     }
286     
287     /**
288      *Returns the logger for the common utils component.
289      *@return the Logger for this component
290      */

291     private static Logger JavaDoc getLogger() {
292         return LogDomains.getLogger(LogDomains.CMN_LOGGER);
293     }
294     
295     /**
296      *Returns a formatted string, using the key to find the full message and
297      *substituting any parameters.
298      *@param key the message key with which to locate the message of interest
299      *@param o the object(s), if any, to be substituted into the message
300      *@return a String containing the formatted message
301      */

302     private static String JavaDoc getMessage(String JavaDoc key, Object JavaDoc... o) {
303         String JavaDoc msg = getLogger().getResourceBundle().getString(key);
304         return MessageFormat.format(msg, o);
305     }
306 }
307
Popular Tags