001: /*
002: * Copyright (C) The MX4J Contributors.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the MX4J License version 1.0.
006: * See the terms of the MX4J License in the documentation provided with this software.
007: */
008:
009: package javax.management.loading;
010:
011: import java.io.BufferedInputStream;
012: import java.io.BufferedOutputStream;
013: import java.io.ByteArrayOutputStream;
014: import java.io.Externalizable;
015: import java.io.File;
016: import java.io.FileOutputStream;
017: import java.io.IOException;
018: import java.io.InputStream;
019: import java.io.ObjectInput;
020: import java.io.ObjectInputStream;
021: import java.io.ObjectOutput;
022: import java.io.OutputStream;
023: import java.io.UnsupportedEncodingException;
024: import java.net.MalformedURLException;
025: import java.net.URL;
026: import java.net.URLClassLoader;
027: import java.net.URLStreamHandlerFactory;
028: import java.security.AccessController;
029: import java.security.PrivilegedAction;
030: import java.util.Arrays;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.List;
034: import java.util.Set;
035: import javax.management.MBeanRegistration;
036: import javax.management.MBeanServer;
037: import javax.management.ObjectInstance;
038: import javax.management.ObjectName;
039: import javax.management.ServiceNotFoundException;
040:
041: import mx4j.loading.ClassLoaderObjectInputStream;
042: import mx4j.loading.MLetParseException;
043: import mx4j.loading.MLetParser;
044: import mx4j.loading.MLetTag;
045: import mx4j.log.Log;
046: import mx4j.log.Logger;
047:
048: /**
049: * @version $Revision: 1.27 $
050: */
051: public class MLet extends URLClassLoader implements MLetMBean,
052: MBeanRegistration, Externalizable {
053: private MBeanServer server;
054: private ObjectName objectName;
055: private boolean delegateToCLR;
056: private ThreadLocal loadingOnlyLocally = new ThreadLocal();
057: private ThreadLocal loadingWithRepository = new ThreadLocal();
058: private String libraryDir;
059:
060: public MLet() {
061: this (new URL[0]);
062: }
063:
064: public MLet(URL[] urls) {
065: this (urls, true);
066: }
067:
068: public MLet(URL[] urls, boolean delegateToCLR) {
069: super (urls);
070: setDelegateToCLR(delegateToCLR);
071: loadingWithRepository.set(Boolean.FALSE);
072: loadingOnlyLocally.set(Boolean.FALSE);
073: }
074:
075: public MLet(URL[] urls, ClassLoader parent) {
076: this (urls, parent, true);
077: }
078:
079: public MLet(URL[] urls, ClassLoader parent, boolean delegateToCLR) {
080: super (urls, parent);
081: setDelegateToCLR(delegateToCLR);
082: loadingWithRepository.set(Boolean.FALSE);
083: loadingOnlyLocally.set(Boolean.FALSE);
084: }
085:
086: public MLet(URL[] urls, ClassLoader parent,
087: URLStreamHandlerFactory factory) {
088: this (urls, parent, factory, true);
089: }
090:
091: public MLet(URL[] urls, ClassLoader parent,
092: URLStreamHandlerFactory factory, boolean delegateToCLR) {
093: super (urls, parent, factory);
094: this .setDelegateToCLR(delegateToCLR);
095: loadingWithRepository.set(Boolean.FALSE);
096: loadingOnlyLocally.set(Boolean.FALSE);
097: }
098:
099: public ObjectName preRegister(MBeanServer server, ObjectName name)
100: throws Exception {
101: this .server = server;
102: objectName = name == null ? new ObjectName(this .server
103: .getDefaultDomain(), "type", "MLet") : name;
104: Logger logger = getLogger();
105: if (logger.isEnabledFor(Logger.TRACE))
106: logger.trace("MLet service " + objectName
107: + " preRegistered successfully");
108: return objectName;
109: }
110:
111: public void postRegister(Boolean registrationDone) {
112: Logger logger = getLogger();
113: if (!registrationDone.booleanValue()) {
114: server = null;
115: if (logger.isEnabledFor(Logger.INFO))
116: logger.info("MLet service " + objectName
117: + " was not registered");
118: } else {
119: if (logger.isEnabledFor(Logger.TRACE))
120: logger.trace("MLet service " + objectName
121: + " postRegistered successfully");
122: }
123: }
124:
125: public void preDeregister() throws Exception {
126: Logger logger = getLogger();
127: if (logger.isEnabledFor(Logger.TRACE))
128: logger.trace("MLet service " + objectName
129: + " preDeregistered successfully");
130: }
131:
132: public void postDeregister() {
133: Logger logger = getLogger();
134: if (logger.isEnabledFor(Logger.TRACE))
135: logger.trace("MLet service " + objectName
136: + " postDeregistered successfully");
137: }
138:
139: public void addURL(String url) throws ServiceNotFoundException {
140: addURL(createURL(url));
141: }
142:
143: public void addURL(URL url) {
144: Logger logger = getLogger();
145: if (!Arrays.asList(getURLs()).contains(url)) {
146: if (logger.isEnabledFor(Logger.TRACE))
147: logger.trace("Adding URL to this MLet (" + objectName
148: + ") classpath: " + url);
149: super .addURL(url);
150: } else {
151: if (logger.isEnabledFor(Logger.TRACE))
152: logger.trace("URL already present in this MLet ("
153: + objectName + ") classpath: " + url);
154: }
155: }
156:
157: public Class loadClass(String name, ClassLoaderRepository repository)
158: throws ClassNotFoundException {
159: if (repository == null) {
160: Class cls = loadClassLocally(name);
161: return cls;
162: } else {
163: try {
164: Class cls = loadClassLocally(name);
165: return cls;
166: } catch (ClassNotFoundException x) {
167: // Not found locally, try the given repository
168: Class cls = loadClassFromRepository(name, repository);
169: return cls;
170: }
171: }
172: }
173:
174: /**
175: * Loads the given class from this MLet only (with the usual parent delegation mechanism); the
176: * ClassLoaderRepository is not asked to load the class.
177: *
178: * @param name The name of the class to load.
179: * @return The loaded class
180: * @throws ClassNotFoundException
181: */
182: private Class loadClassLocally(String name)
183: throws ClassNotFoundException {
184: // Here I must call super.loadClass(name) but not delegate to the CLR when I arrive to MLet.findClass
185: // I cannot call findClassLocally directly because otherwise the mechanism of parent delegation
186: // implemented in loadClass() is skipped and the parent loaders do not have the chance to
187: // load the given class
188:
189: try {
190: loadingOnlyLocally.set(Boolean.TRUE);
191: return loadClass(name);
192: } finally {
193: loadingOnlyLocally.set(Boolean.FALSE);
194: }
195: }
196:
197: /**
198: * Loads the given class from the given non-null ClassLoaderRepository using
199: * {@link ClassLoaderRepository#loadClassBefore}
200: */
201: private Class loadClassFromRepository(String name,
202: ClassLoaderRepository repository)
203: throws ClassNotFoundException {
204: return repository.loadClassBefore(this , name);
205: }
206:
207: protected Class findClass(String name)
208: throws ClassNotFoundException {
209: Logger logger = getLogger();
210: boolean trace = logger.isEnabledFor(Logger.TRACE);
211:
212: // It may be possible that loading started with this MLet, then delegated to the CLR
213: // and came again to query this MLet which, if not stopped, will delegate to the CLR
214: // again in an endless loop. This is possible if this MLet is the parent of a child
215: // MLet registered before its parent.
216: if (loadingWithRepository.get() == Boolean.TRUE) {
217: if (trace)
218: logger
219: .trace("MLet "
220: + this
221: + " is recursively calling itself to load class "
222: + name + ": skipping further searches");
223: throw new ClassNotFoundException(name);
224: }
225:
226: if (trace)
227: logger.trace("Finding class " + name + "...");
228:
229: try {
230: Class cls = findClassLocally(name);
231: if (trace)
232: logger.trace("Class " + name
233: + " found in this MLet's classpath " + this );
234: return cls;
235: } catch (ClassNotFoundException x) {
236: if (!isDelegateToCLR()) {
237: if (trace)
238: logger
239: .trace("MLet "
240: + this
241: + " does not delegate to the ClassLoaderRepository");
242: throw x;
243: }
244:
245: if (loadingOnlyLocally.get() == Boolean.TRUE)
246: throw x;
247:
248: if (server == null)
249: throw x;
250:
251: if (trace)
252: logger.trace("Class " + name
253: + " not found in this MLet's classpath " + this
254: + ", trying the ClassLoaderRepository...", x);
255: try {
256: loadingWithRepository.set(Boolean.TRUE);
257: ClassLoaderRepository repository = server
258: .getClassLoaderRepository();
259: Class cls = loadClassFromRepository(name, repository);
260: if (trace)
261: logger.trace("Class " + name
262: + " found with ClassLoaderRepository "
263: + repository);
264: return cls;
265: } catch (ClassNotFoundException xx) {
266: if (trace)
267: logger
268: .trace(
269: "Class "
270: + name
271: + " not found in ClassLoaderRepository, giving up",
272: xx);
273: throw new ClassNotFoundException(name);
274: } finally {
275: loadingWithRepository.set(Boolean.FALSE);
276: }
277: }
278: }
279:
280: private Class findClassLocally(String name)
281: throws ClassNotFoundException {
282: return super .findClass(name);
283: }
284:
285: public Set getMBeansFromURL(String url)
286: throws ServiceNotFoundException {
287: return getMBeansFromURL(createURL(url));
288: }
289:
290: public Set getMBeansFromURL(URL url)
291: throws ServiceNotFoundException {
292: if (url == null)
293: throw new ServiceNotFoundException(
294: "Cannot load MBeans from null URL");
295:
296: Logger logger = getLogger();
297: if (logger.isEnabledFor(Logger.TRACE))
298: logger.trace("MLet " + this + ", reading MLET file from "
299: + url);
300:
301: InputStream is = null;
302: ByteArrayOutputStream baos = new ByteArrayOutputStream();
303: BufferedOutputStream os = new BufferedOutputStream(baos);
304: try {
305: is = url.openStream();
306: readFromAndWriteTo(is, os);
307: } catch (IOException x) {
308: if (logger.isEnabledFor(Logger.TRACE))
309: logger.trace(
310: "Cannot read input stream from URL " + url, x);
311: throw new ServiceNotFoundException(x.toString());
312: } finally {
313: try {
314: if (is != null)
315: is.close();
316: os.close();
317: } catch (IOException ignored) {
318: }
319: }
320:
321: String mletFileContent = null;
322: try {
323: mletFileContent = new String(baos.toByteArray(), "UTF-8");
324: } catch (UnsupportedEncodingException x) {
325: mletFileContent = baos.toString();
326: }
327: if (logger.isEnabledFor(Logger.TRACE))
328: logger.trace("MLet File content is:\n" + mletFileContent);
329:
330: return parseMLetFile(mletFileContent, url);
331: }
332:
333: private Set parseMLetFile(String content, URL mletFileURL)
334: throws ServiceNotFoundException {
335: Logger logger = getLogger();
336:
337: try {
338: HashSet mbeans = new HashSet();
339: MLetParser parser = new MLetParser(this );
340: List tags = parser.parse(content);
341:
342: for (int i = 0; i < tags.size(); ++i) {
343: MLetTag tag = (MLetTag) tags.get(i);
344:
345: // Add the MBean's codebase to the MLet classloader
346: String[] jars = tag.parseArchive();
347: for (int j = 0; j < jars.length; ++j) {
348: String jar = jars[j];
349: URL codebase = handleCheck(tag, jar, mletFileURL,
350: mbeans);
351: URL archiveURL = tag
352: .createArchiveURL(codebase, jar);
353: addURL(archiveURL);
354: }
355:
356: // Create and register the MBean
357: Object obj = createMBean(tag);
358: mbeans.add(obj);
359: }
360:
361: return mbeans;
362: } catch (MLetParseException x) {
363: if (logger.isEnabledFor(Logger.TRACE))
364: logger.trace("Cannot parse MLet file", x);
365: throw new ServiceNotFoundException(x.toString());
366: }
367: }
368:
369: /**
370: * This method handle the call to {@link #check}, a method that it's a big mistake in JMX 1.2
371: */
372: private URL handleCheck(MLetTag tag, String archive,
373: URL mletFileURL, Set mbeans) {
374: HashMap map = new HashMap();
375: map.put("codebaseURL", tag.normalizeCodeBase(mletFileURL));
376: map.put("codebase", tag.getCodeBase());
377: map.put("archive", tag.getArchive());
378: map.put("code", tag.getCode());
379: map.put("object", tag.getObject());
380: map.put("name", tag.getObjectName());
381: map.put("version", tag.getVersion());
382: MLetContent mletContent = new MLetContent(mletFileURL, map);
383:
384: try {
385: // JMX 1.2 FAAAAANTASTIC check method
386: return check(mletContent.getVersion(), mletContent
387: .getCodeBase(), archive, mletContent);
388: } catch (Throwable x) {
389: mbeans.add(x);
390: return null;
391: }
392: }
393:
394: /**
395: * This method is called when an MLet file has been parsed and before its information is used by this MLet.
396: * By overriding this method subclasses have the possibility to perform caching and versioning of jars,
397: * but unfortunately it contains as parameter a package private class, that thus forbids overriding: a big
398: * mistake in the JMX 1.2 specification.
399: *
400: * @since JMX 1.2
401: */
402: protected URL check(String version, URL codebase, String archive,
403: MLetContent content) throws Exception {
404: return codebase;
405: }
406:
407: private Object createMBean(MLetTag tag)
408: throws ServiceNotFoundException {
409: if (server == null)
410: throw new ServiceNotFoundException(
411: "MLet not registered on the MBeanServer");
412:
413: Logger logger = getLogger();
414: if (logger.isEnabledFor(Logger.TRACE))
415: logger.trace("MLet " + this + ", creating MBean from\n"
416: + tag);
417:
418: try {
419: Object mbean = null;
420: if (tag.getObject() != null) {
421: // Read the file from the codebase URLs of this classloader
422: String name = tag.getObject();
423: InputStream is = getResourceAsStream(name);
424: if (is == null)
425: throw new ServiceNotFoundException(
426: "Cannot find serialized MBean " + name
427: + " in MLet " + this );
428:
429: InputStream bis = new BufferedInputStream(is);
430:
431: // Deserialize using the MLet classloader
432: ObjectInputStream ois = new ClassLoaderObjectInputStream(
433: bis, this );
434: mbean = ois.readObject();
435: } else {
436: // Instantiate using the MLet classloader
437: String clsName = tag.getCode();
438: Object[] args = tag.getArguments();
439: String[] params = tag.getSignature();
440: mbean = server.instantiate(clsName, objectName, args,
441: params);
442: }
443:
444: ObjectName objectName = tag.getObjectName();
445: ObjectInstance instance = server.registerMBean(mbean,
446: objectName);
447: return instance;
448: } catch (Throwable t) {
449: return t;
450: }
451: }
452:
453: protected String findLibrary(String libraryName) {
454: // When asked to load a library, I should convert the library name to the system specific name
455: // For example if libraryName == "stat", on Solaris the conversion yields libstat.so, on Windows yields stat.dll
456: final String sysLibraryName = System
457: .mapLibraryName(libraryName);
458:
459: // Try to load the native libaray from jar only using the library name
460: String path = copyLibrary(sysLibraryName);
461: if (path != null)
462: return path;
463:
464: // Library not found so try to load the library using the os specific architecture
465: String osPath = (String) AccessController
466: .doPrivileged(new PrivilegedAction() {
467: public Object run() {
468: StringBuffer buffer = new StringBuffer();
469: buffer.append(System.getProperty("os.name"))
470: .append(File.separator);
471: buffer.append(System.getProperty("os.arch"))
472: .append(File.separator);
473: buffer.append(System.getProperty("os.version"))
474: .append(File.separator);
475: buffer.append("lib").append(File.separator)
476: .append(sysLibraryName);
477: return buffer.toString();
478: }
479: });
480:
481: osPath = removeSpaces(osPath);
482:
483: return copyLibrary(osPath);
484: }
485:
486: private String copyLibrary(String library) {
487: Logger logger = getLogger();
488:
489: library = library.replace('\\', '/');
490: if (logger.isEnabledFor(Logger.TRACE))
491: logger.trace("Loading library " + library);
492:
493: URL libraryURL = getResource(library);
494:
495: InputStream is = null;
496: OutputStream os = null;
497: try {
498: try {
499: is = getResourceAsStream(library);
500: if (is == null)
501: return null;
502:
503: if (!(is instanceof BufferedInputStream))
504: is = new BufferedInputStream(is);
505: File localLibrary = new File(getLibraryDirectory(),
506: library);
507: URL localLibraryURL = localLibrary.toURL();
508:
509: // The library is local and its directory is in the classpath of this MLet
510: if (localLibraryURL.equals(libraryURL))
511: return localLibrary.getCanonicalPath();
512:
513: // Copy the library (that can be remote) locally, overwriting old versions
514: try {
515: os = new BufferedOutputStream(new FileOutputStream(
516: localLibrary));
517: readFromAndWriteTo(is, os);
518: return localLibrary.getCanonicalPath();
519: } finally {
520: if (os != null)
521: os.close();
522: }
523: } finally {
524: if (is != null)
525: is.close();
526: }
527: } catch (IOException x) {
528: if (logger.isEnabledFor(Logger.TRACE))
529: logger.trace(
530: "Cannot copy the library to the library directory "
531: + getLibraryDirectory(), x);
532: return null;
533: }
534: }
535:
536: private void readFromAndWriteTo(InputStream is, OutputStream os)
537: throws IOException {
538: byte[] buffer = new byte[64];
539: int read = -1;
540: while ((read = is.read(buffer)) >= 0)
541: os.write(buffer, 0, read);
542: }
543:
544: private String removeSpaces(String string) {
545: int space = -1;
546: StringBuffer buffer = new StringBuffer();
547: while ((space = string.indexOf(' ')) >= 0) {
548: buffer.append(string.substring(0, space));
549: string = string.substring(space + 1);
550: }
551: buffer.append(string);
552: return buffer.toString();
553: }
554:
555: public String getLibraryDirectory() {
556: return libraryDir;
557: }
558:
559: public void setLibraryDirectory(String libdir) {
560: libraryDir = libdir;
561: }
562:
563: private boolean isDelegateToCLR() {
564: return delegateToCLR;
565: }
566:
567: private void setDelegateToCLR(boolean delegateToCLR) {
568: this .delegateToCLR = delegateToCLR;
569: }
570:
571: private URL createURL(String urlString)
572: throws ServiceNotFoundException {
573: try {
574: URL url = new URL(urlString);
575: return url;
576: } catch (MalformedURLException x) {
577: throw new ServiceNotFoundException(x.toString());
578: }
579: }
580:
581: private Logger getLogger() {
582: return Log.getLogger(getClass().getName());
583: }
584:
585: /**
586: * Restores this MLet content from the given ObjectInput. Implementation of this method is optional,
587: * and if not implemented throws an UnsupportedOperationException.
588: */
589: public void readExternal(ObjectInput in) throws IOException,
590: ClassNotFoundException {
591: throw new UnsupportedOperationException("MLet.readExternal");
592: }
593:
594: /**
595: * Stores this MLet content in the given ObjectOutput. Implementation of this method is optional,
596: * and if not implemented throws an UnsupportedOperationException.
597: */
598: public void writeExternal(ObjectOutput out) throws IOException {
599: throw new UnsupportedOperationException("MLet.writeExternal");
600: }
601: }
|