001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.mx.util;
023:
024: import java.beans.PropertyEditor;
025: import java.beans.PropertyEditorManager;
026: import java.io.InputStream;
027: import java.util.Date;
028: import java.util.List;
029: import java.util.HashMap;
030: import java.util.Map;
031:
032: import javax.management.InstanceNotFoundException;
033: import javax.management.MalformedObjectNameException;
034: import javax.management.MBeanException;
035: import javax.management.MBeanServer;
036: import javax.management.ObjectName;
037: import javax.management.ObjectInstance;
038: import javax.management.ReflectionException;
039:
040: import org.jboss.logging.Logger;
041: import org.jboss.mx.server.ObjectInputStreamWithClassLoader;
042: import org.jboss.mx.server.ServerConstants;
043: import org.jboss.mx.loading.MBeanElement;
044: import org.jboss.util.UnreachableStatementException;
045:
046: /**
047: * MBean installer utility<p>
048: *
049: * This installer allows MLet to install or upgrade a mbean based on the version
050: * specified in the MLet conf file. If the mbean version is newer than the
051: * registered in the server, the installer unregisters the old mbean and then
052: * registers the new one. This management needs to store the mbean version into
053: * the MBeanRegistry in the server.
054: *
055: * When we register mbeans, however, we can't pass the metadata to MBeanServer
056: * through the standard JMX api because Both of createMBean() and registerMBean()
057: * have no extra arguments to attach the metadata. Thus we call
058: * MBeanServer.invoke() directly to set/get the internal MBean metadata.
059: *
060: * Currently version and date are stored in the mbean registry as mbean metadata.
061: * The date will be used for preparing presentaionString for this mbean info.
062: * For managment purpose, we can add any extra data to the matadata if you need.
063: *
064: * @author <a href="mailto:Fusayuki.Minamoto@fujixerox.co.jp">Fusayuki Minamoto</a>.
065: * @author <a href="mailto:jplindfo@helsinki.fi">Juha Lindfors</a>.
066: *
067: * @version $Revision: 57200 $
068: *
069: * <p><b>Revisions:</b>
070: *
071: * <p><b>20020219 Juha Lindfors:</b>
072: * <ul>
073: * <li>Clarified the use of classloaders in the code, renaming loader to a more
074: * explicit ctxClassLoader
075: * </li>
076: * <li>Fixed some irregularities with the install/update code -- original
077: * implementatio was cause IndexOutOfBoundsExceptions which prevented
078: * some replacements in valid cases. Fixing this uncovered update logic
079: * that would replace MBeans that were not associated with versioning
080: * information at all.
081: * <p>
082: * The current semantics should be:
083: * <ol>
084: * <li>If an MBean is registered without versioning information it can
085: * never be automatically replaced by another MBean (regardless of
086: * the versioning information in the new MBean).
087: * </li>
088: * <li>An MBean that has a higher version number (as determined by the
089: * MLetVersion Comparable interface) can automatically replace an
090: * MBean that was registered with a lower version number.
091: * </li>
092: * <li>An MBean without versioning info can never automatically replace
093: * an MBean that was registered with version.
094: * </li>
095: * </ol>
096: * </li>
097: * </ul>
098: */
099: public class MBeanInstaller {
100: // Constants -----------------------------------------------------
101:
102: public static final String VERSIONS = "versions";
103: public static final String DATE = "date";
104:
105: // Static --------------------------------------------------------
106:
107: /**
108: * Logger instance.
109: */
110: private static final Logger log = Logger
111: .getLogger(MBeanInstaller.class);
112:
113: /** Augment the PropertyEditorManager search path to incorporate the JBoss
114: specific editors. This simply references the PropertyEditors.class to
115: invoke its static initialization block.
116: */
117: static {
118: Class c = org.jboss.util.propertyeditor.PropertyEditors.class;
119: if (c == null)
120: throw new UnreachableStatementException();
121: }
122:
123: // Attributes ----------------------------------------------------
124:
125: /**
126: * Reference to the MBean server the installed MBeans will get registered to.
127: */
128: private MBeanServer server;
129:
130: /**
131: * Reference to the context classloader of the MLet MBean that is installing
132: * the new MBeans.
133: */
134: private ClassLoader ctxClassLoader;
135:
136: /**
137: * Object name of the MLet MBean installing new MBeans to the server. This
138: * object name is used as the explicit classloader object name when
139: * instantiating new MBeans. This is to ensure the MLet's classloader is the
140: * first one to be consulted when loading classes. This is implicitly
141: * guaranteed by the UnifiedLoaderRepository but is not necessarily the case
142: * with other loader repository implementations.
143: */
144: private ObjectName loaderName;
145:
146: /**
147: * Object name of the MBeanServer registry MBean.
148: */
149: private ObjectName registryName;
150:
151: // Constructors --------------------------------------------------
152:
153: /**
154: * Create a new MBean installer instance.
155: *
156: * @param server reference to the MBean server where the new MBeans will
157: * be registered to
158: * @param ctxClassLoader Context class loader reference which will be
159: * stored in the registry for the new MBeans. This
160: * classloader will be set as the thread context
161: * classloader when the MBean is invoked.
162: * @param loaderName Object name of the classloader that should be
163: * used to instantiate the newly registered MBeans.
164: * This should normally be the object name of the
165: * MLet MBean that is installing the new MBeans.
166: */
167: public MBeanInstaller(MBeanServer server,
168: ClassLoader ctxClassLoader, ObjectName loaderName)
169: throws Exception {
170: this .server = server;
171: this .ctxClassLoader = ctxClassLoader;
172: this .loaderName = loaderName;
173: this .registryName = new ObjectName(
174: ServerConstants.MBEAN_REGISTRY);
175: }
176:
177: // Public --------------------------------------------------------
178:
179: /**
180: * Install a mbean with mbean metadata<p>
181: *
182: * @param element the data parsed from the Mlet file
183: *
184: * @return mbean instance
185: */
186: public ObjectInstance installMBean(MBeanElement element)
187: throws MBeanException, ReflectionException,
188: InstanceNotFoundException, MalformedObjectNameException {
189: log.debug("Installing MBean: " + element);
190:
191: ObjectInstance instance = null;
192: ObjectName elementName = getElementName(element);
193:
194: if (element.getVersions().isEmpty()
195: || !server.isRegistered(elementName)) {
196: if (element.getCode() != null)
197: instance = createMBean(element);
198: else if (element.getObject() != null)
199: instance = deserialize(element);
200: else
201: throw new MBeanException(new IllegalArgumentException(
202: "No code or object tag"));
203: } else
204: instance = updateMBean(element);
205:
206: return instance;
207: }
208:
209: public ObjectInstance createMBean(MBeanElement element)
210: throws MBeanException, ReflectionException,
211: InstanceNotFoundException, MalformedObjectNameException {
212: log.debug("Creating MBean.. ");
213:
214: ObjectName elementName = getElementName(element);
215:
216: // Set up the valueMap passing to the registry.
217: // This valueMap contains mbean meta data and update time.
218: Map valueMap = createValueMap(element);
219:
220: // Create the mbean instance
221:
222: // TODO:
223: // check the delegateToCLR attribute in the MLetElement here to determine
224: // the loading behavior in case of CNFE
225:
226: String[] classes = element.getConstructorTypes();
227: String[] paramStrings = element.getConstructorValues();
228: Object[] params = new Object[paramStrings.length];
229: for (int i = 0; i < paramStrings.length; ++i) {
230: try {
231: Class typeClass = server.getClassLoaderRepository()
232: .loadClass(classes[i]);
233: PropertyEditor editor = PropertyEditorManager
234: .findEditor(typeClass);
235: if (editor == null)
236: throw new IllegalArgumentException(
237: "No property editor for type=" + typeClass);
238:
239: editor.setAsText(paramStrings[i]);
240: params[i] = editor.getValue();
241: } catch (Exception e) {
242: throw new MBeanException(e);
243: }
244: }
245: Object instance = server.instantiate(element.getCode(),
246: loaderName, params, classes);
247:
248: // Call MBeanRegistry.invoke("registerMBean") instead of server.registerMBean() to pass
249: // the valueMap that contains management values including mbean metadata and update time.
250: return registerMBean(instance, elementName, valueMap);
251: }
252:
253: public ObjectInstance deserialize(MBeanElement element)
254: throws MBeanException, ReflectionException,
255: InstanceNotFoundException, MalformedObjectNameException {
256: InputStream is = null;
257: Object instance = null;
258: try {
259: is = ctxClassLoader
260: .getResourceAsStream(element.getObject());
261: if (is == null)
262: throw new IllegalArgumentException("Object not found "
263: + element.getObject());
264: ObjectInputStreamWithClassLoader ois = new ObjectInputStreamWithClassLoader(
265: is, ctxClassLoader);
266: instance = ois.readObject();
267: } catch (Exception e) {
268: throw new MBeanException(e);
269: } finally {
270: if (is != null) {
271: try {
272: is.close();
273: } catch (Exception ignored) {
274: }
275: }
276: }
277: ObjectName elementName = getElementName(element);
278:
279: // Set up the valueMap passing to the registry.
280: // This valueMap contains mbean meta data and update time.
281: Map valueMap = createValueMap(element);
282: return registerMBean(instance, elementName, valueMap);
283: }
284:
285: public ObjectInstance updateMBean(MBeanElement element)
286: throws MBeanException, ReflectionException,
287: InstanceNotFoundException, MalformedObjectNameException {
288: log.debug("updating MBean... ");
289:
290: ObjectName elementName = getElementName(element);
291:
292: // Compare versions to decide whether to skip installation of this mbean
293: MLetVersion preVersion = new MLetVersion(
294: getVersions(elementName));
295: MLetVersion newVersion = new MLetVersion(element.getVersions());
296:
297: log.debug("Installed version : " + preVersion);
298: log.debug("Loaded version : " + newVersion);
299:
300: // FIXME: this comparison works well only if both versions are specified
301: // because jmx spec doesn't fully specify this behavior.
302: if (!preVersion.isNull() && !newVersion.isNull()
303: && preVersion.compareTo(newVersion) < 0) {
304: // Unregister previous mbean
305: if (server.isRegistered(elementName)) {
306: unregisterMBean(elementName);
307:
308: log.debug("Unregistering previous version "
309: + preVersion);
310: }
311:
312: log.debug("Installing newer version " + newVersion);
313:
314: // Create mbean with value map
315: return createMBean(element);
316: }
317:
318: return server.getObjectInstance(elementName);
319: }
320:
321: // Private -------------------------------------------------------
322:
323: private ObjectName getElementName(MBeanElement element)
324: throws MalformedObjectNameException {
325: return (element.getName() != null) ? new ObjectName(element
326: .getName()) : null;
327: }
328:
329: private Map createValueMap(MBeanElement element) {
330: HashMap valueMap = new HashMap();
331:
332: // We need to set versions here because we can't get the mbean entry
333: // outside the server.
334: if (element.getVersions() != null
335: && !element.getVersions().isEmpty())
336: valueMap.put(VERSIONS, element.getVersions());
337:
338: // The date would be used to make a presentationString for this mbean.
339: valueMap.put(DATE, new Date(System.currentTimeMillis()));
340:
341: // Context class loader for the MBean.
342: valueMap.put(ServerConstants.CLASSLOADER, ctxClassLoader);
343:
344: return valueMap;
345: }
346:
347: private List getVersions(ObjectName name) throws MBeanException,
348: ReflectionException, InstanceNotFoundException {
349: if (!server.isRegistered(name))
350: return null;
351:
352: return (List) getValue(name, VERSIONS);
353: }
354:
355: private Object getValue(ObjectName name, String key)
356: throws MBeanException, ReflectionException,
357: InstanceNotFoundException {
358: Object value = server.invoke(registryName, "getValue",
359: new Object[] { name, key }, new String[] {
360: ObjectName.class.getName(),
361: String.class.getName() });
362:
363: return value;
364: }
365:
366: private ObjectInstance registerMBean(Object object,
367: ObjectName name, Map valueMap) throws MBeanException,
368: ReflectionException, InstanceNotFoundException {
369: if (object == null) {
370: throw new ReflectionException(new IllegalArgumentException(
371: "Attempting to register a null object"));
372: }
373:
374: return (ObjectInstance) server
375: .invoke(registryName, "registerMBean", new Object[] {
376: object, name, valueMap },
377: new String[] { Object.class.getName(),
378: ObjectName.class.getName(),
379: Map.class.getName() });
380: }
381:
382: private void unregisterMBean(ObjectName name)
383: throws MBeanException, ReflectionException,
384: InstanceNotFoundException {
385: server.invoke(registryName, "unregisterMBean",
386: new Object[] { name, }, new String[] { ObjectName.class
387: .getName(), });
388: }
389: }
390:
391: /**
392: * MLetVersion for encapsulating the version representation<p>
393: *
394: * Because this class is comparable, you can elaborate the
395: * version comparison algorithm if you need better one.
396: */
397: class MLetVersion implements Comparable {
398: protected List versions;
399:
400: public MLetVersion(List versions) {
401: this .versions = versions;
402: }
403:
404: public List getVersions() {
405: return versions;
406: }
407:
408: public boolean isNull() {
409: return versions == null || versions.isEmpty();
410: }
411:
412: public int compareTo(Object o) {
413: MLetVersion other = (MLetVersion) o;
414:
415: if (isNull() || other.isNull())
416: throw new IllegalArgumentException("MLet versions is null");
417:
418: // FIXME: this compares only first element of the versions.
419: // do we really need multiple versions?
420: String this Version = (String) versions.get(0);
421: String otherVersion = (String) other.getVersions().get(0);
422:
423: return (this Version.compareTo(otherVersion));
424: }
425:
426: public String toString() {
427: return "Version " + versions.get(0);
428: }
429: }
|