From b5cf24853e7719b43a23b47bf595abb61dc25b3a Mon Sep 17 00:00:00 2001 From: emcmanus Date: Wed, 10 Dec 2008 11:59:32 +0100 Subject: [PATCH] 6456269: Add a GenericMBeanException so clients don't have to have server's exception classes present Reviewed-by: jfdenise, dfuchs --- .../classes/javax/management/Descriptor.java | 34 +++ .../management/GenericMBeanException.java | 276 +++++++++++++++++ .../javax/management/MBeanException.java | 64 +++- .../interop/MBeanExceptionInteropTest.java | 166 +++++++++++ .../openmbean/GenericMBeanExceptionTest.java | 278 ++++++++++++++++++ 5 files changed, 807 insertions(+), 11 deletions(-) create mode 100644 src/share/classes/javax/management/GenericMBeanException.java create mode 100644 test/javax/management/interop/MBeanExceptionInteropTest.java create mode 100644 test/javax/management/openmbean/GenericMBeanExceptionTest.java diff --git a/src/share/classes/javax/management/Descriptor.java b/src/share/classes/javax/management/Descriptor.java index 30c6a5f52..573f8fdbe 100644 --- a/src/share/classes/javax/management/Descriptor.java +++ b/src/share/classes/javax/management/Descriptor.java @@ -155,6 +155,23 @@ import javax.management.openmbean.OpenType; * setting an attribute are specified by the field * {@code setExceptions}. * + * exceptionErrorCodesString[] + * MBeanAttributeInfo
MBeanConstructorInfo
MBeanOperationInfo + * + * The {@linkplain GenericMBeanException#getErrorCode() error codes} + * that can appear in a {@link GenericMBeanException} thrown when getting + * this attribute or invoking this operation or constructor. See also + * {@code setExceptionErrorCodes}. + * + * exceptionUserDataTypes + * {@link javax.management.openmbean.CompositeType}[] + * MBeanAttributeInfo
MBeanConstructorInfo
MBeanOperationInfo + * + * The types of {@linkplain GenericMBeanException#getUserData() userData} + * that can appear in a {@link GenericMBeanException} thrown when getting + * this attribute or invoking this operation or constructor. See also + * {@code setExceptionUserDataTypes}. + * * immutableInfoString * MBeanInfo * @@ -292,6 +309,23 @@ import javax.management.openmbean.OpenType; * an attribute. Exceptions thrown when getting an attribute are specified * by the field {@code exceptions}. * + * setExceptionErrorCodes + * String[]MBeanAttributeInfo + * + * The {@linkplain GenericMBeanException#getErrorCode() error codes} + * that can appear in a {@link GenericMBeanException} thrown when setting + * this attribute. See also + * {@code exceptionErrorCodes}. + * + * setExceptionUserDataTypes + * {@link javax.management.openmbean.CompositeType}[] + * MBeanAttributeInfo + * + * The types of {@linkplain GenericMBeanException#getUserData() userData} + * that can appear in a {@link GenericMBeanException} thrown when setting + * this attribute. See also + * {@code exceptionUserDataTypes}. + * * severityString
Integer * MBeanNotificationInfo * diff --git a/src/share/classes/javax/management/GenericMBeanException.java b/src/share/classes/javax/management/GenericMBeanException.java new file mode 100644 index 000000000..dd0f96e78 --- /dev/null +++ b/src/share/classes/javax/management/GenericMBeanException.java @@ -0,0 +1,276 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package javax.management; + +import javax.management.openmbean.CompositeData; + +/** + *

A customizable exception that has an optional error code string and + * payload. By using this exception in an MBean, you can avoid requiring + * clients of the MBean to have custom exception classes.

+ * + *

An instance of this class has an optional {@linkplain #getErrorCode() + * error code}, and an optional {@linkplain #getUserData() payload} known as + * {@code userData}. This allows you to distinguish between different + * sorts of exception while still using this class for all of them.

+ * + *

To produce a suitable {@code userData}, it is often simplest to use + * the MXBean framework. For example, suppose you want to convey a severity + * and a subsystem with your exception, which are respectively an int and a + * String. You could define a class like this:

+ * + *
+ * public class ExceptionDetails {
+ *     private final int severity;
+ *     private final String subsystem;
+ *
+ *     {@link java.beans.ConstructorProperties @ConstructorProperties}({"severity", "subsystem"})
+ *     public ExceptionDetails(int severity, String subsystem) {
+ *         this.severity = severity;
+ *         this.subsystem = subsystem;
+ *     }
+ *
+ *     public int getSeverity() {
+ *         return severity;
+ *     }
+ *
+ *     public String getSubsystem() {
+ *         return subsystem;
+ *     }
+ * }
+ * 
+ * + *

Then you can get the MXBean framework to transform {@code ExceptionDetails} + * into {@link CompositeData} like this:

+ * + *
+ * static final {@link javax.management.openmbean.MXBeanMapping MXBeanMapping} exceptionDetailsMapping = {@link javax.management.openmbean.MXBeanMappingFactory#DEFAULT
+ *     MXBeanMappingFactory.DEFAULT}.mappingForType(
+ *         ExceptionDetails.class, MXBeanMappingFactory.DEFAULT);
+ *
+ * public static GenericMBeanException newGenericMBeanException(
+ *         String message, String errorCode, int severity, String subsystem) {
+ *     ExceptionDetails details = new ExceptionDetails(severity, subsystem);
+ *     CompositeData userData = (CompositeData)
+ *             exceptionDetailsMapping.toOpenValue(details);
+ *     return new GenericMBeanException(
+ *             message, errorCode, userData, (Throwable) null);
+ * }
+ *
+ * ...
+ *     throw newGenericMBeanException(message, errorCode, 25, "foosystem");
+ * 
+ * + *

A client that knows the {@code ExceptionDetails} class can convert + * back from the {@code userData} of a {@code GenericMBeanException} + * that was generated as above:

+ * + *
+ * ...
+ *     try {
+ *         mbeanProxy.foo();
+ *     } catch (GenericMBeanException e) {
+ *         CompositeData userData = e.getUserData();
+ *         ExceptionDetails details = (ExceptionDetails)
+ *                 exceptionDetailsMapping.fromOpenValue(userData);
+ *         System.out.println("Exception Severity: " + details.getSeverity());
+ *     }
+ * ...
+ * 
+ * + *

The Descriptor field exceptionErrorCodes can be used to specify in the + * {@link MBeanOperationInfo} for an operation what the possible + * {@linkplain #getErrorCode() error codes} are when that operation throws + * {@code GenericMBeanException}. It can also be used in an {@link + * MBeanConstructorInfo} or {@link MBeanAttributeInfo} to specify what the + * possible error codes are for {@code GenericMBeanException} when invoking + * that constructor or getting that attribute, respectively. The field + * setExceptionErrorCodes can be used to specify what the possible + * error codes are when setting an attribute.

+ * + *

You may want to use the {@link DescriptorKey @DescriptorKey} facility + * to define annotations that allow you to specify the error codes. If you + * define...

+ * + *
+ * {@link java.lang.annotation.Documented @Documented}
+ * {@link java.lang.annotation.Target @Target}(ElementType.METHOD)
+ * {@link java.lang.annotation.Retention @Retention}(RetentionPolicy.RUNTIME)
+ * public @interface ErrorCodes {
+ *     @DescriptorKey("exceptionErrorCodes")
+ *     String[] value();
+ * }
+ * 
+ * + *

...then you can write MBean interfaces like this...

+ * + *
+ * public interface FooMBean {  // or FooMXBean
+ *     @ErrorCodes({"com.example.bad", "com.example.worse"})
+ *     public void foo() throws GenericMBeanException;
+ * }
+ * 
+ * + *

The Descriptor field exceptionUserDataTypes can be used to specify in the + * {@link MBeanOperationInfo} for an operation what the possible types of + * {@linkplain #getUserData() userData} are when that operation throws + * {@code GenericMBeanException}. It is an array of + * {@link javax.management.openmbean.CompositeType CompositeType} values + * describing the possible {@link CompositeData} formats. This field can also be used + * in an {@link MBeanConstructorInfo} or {@link MBeanAttributeInfo} to specify + * the possible types of user data for {@code GenericMBeanException} when + * invoking that constructor or getting that attribute, respectively. The + * field + * setExceptionUserDataTypes + * can be used to specify the possible types of user data for exceptions when + * setting an attribute. If a Descriptor has both {@code exceptionErrorCodes} + * and {@code exceptionUserDataTypes} then the two arrays should be the + * same size; each pair of corresponding elements describes one kind + * of exception. Similarly for {@code setExceptionErrorCodes} and {@code + * setExceptionUserDataTypes}. + * + * + *

Serialization

+ * + *

For compatibility reasons, instances of this class are serialized as + * instances of {@link MBeanException}. Special logic in that class converts + * them back to instances of this class at deserialization time. If the + * serialized object is deserialized in an earlier version of the JMX API + * that does not include this class, then it will appear as just an {@code + * MBeanException} and the error code or userData will not be available.

+ * + * @since 1.7 + */ +public class GenericMBeanException extends MBeanException { + private static final long serialVersionUID = -1560202003985932823L; + + /** + *

Constructs a new {@code GenericMBeanException} with the given + * detail message. This constructor is + * equivalent to {@link #GenericMBeanException(String, String, + * CompositeData, Throwable) GenericMBeanException(message, "", + * null, null)}.

+ * + * @param message the exception detail message. + */ + public GenericMBeanException(String message) { + this(message, "", null, null); + } + + /** + *

Constructs a new {@code GenericMBeanException} with the given + * detail message and cause. This constructor is + * equivalent to {@link #GenericMBeanException(String, String, + * CompositeData, Throwable) GenericMBeanException(message, "", + * null, cause)}.

+ * + * @param message the exception detail message. + * @param cause the cause of this exception. Can be null. + */ + public GenericMBeanException(String message, Throwable cause) { + this(message, "", null, cause); + } + + /** + *

Constructs a new {@code GenericMBeanException} with the given + * detail message, error code, and user data. This constructor is + * equivalent to {@link #GenericMBeanException(String, String, + * CompositeData, Throwable) GenericMBeanException(message, errorCode, + * userData, null)}.

+ * + * @param message the exception detail message. + * @param errorCode the exception error code. Specifying a null value + * is equivalent to specifying an empty string. It is recommended to use + * the same reverse domain name convention as package names, for example + * "com.example.foo.UnexpectedFailure". There is no requirement that the + * error code be a syntactically valid Java identifier. + * @param userData extra information about the exception. Can be null. + */ + public GenericMBeanException( + String message, String errorCode, CompositeData userData) { + this(message, errorCode, userData, null); + } + + /** + *

Constructs a new {@code GenericMBeanException} with the given + * detail message, error code, user data, and cause.

+ * + * @param message the exception detail message. + * @param errorCode the exception error code. Specifying a null value + * is equivalent to specifying an empty string. It is recommended to use + * the same reverse domain name convention as package names, for example + * "com.example.foo.UnexpectedFailure". There is no requirement that the + * error code be a syntactically valid Java identifier. + * @param userData extra information about the exception. Can be null. + * @param cause the cause of this exception. Can be null. + */ + public GenericMBeanException( + String message, String errorCode, CompositeData userData, + Throwable cause) { + super(message, (errorCode == null) ? "" : errorCode, userData, cause); + } + + /** + *

Returns the error code of this exception.

+ * + * @return the error code. This value is never null. + */ + public String getErrorCode() { + return errorCode; + } + + /** + *

Returns the userData of this exception.

+ * + * @return the userData. Can be null. + */ + public CompositeData getUserData() { + return userData; + } + + /** + *

Instances of this class are serialized as instances of + * {@link MBeanException}. {@code MBeanException} has private fields that can + * not be set by its public constructors. They can only be set in objects + * returned by this method. When an {@code MBeanException} instance is + * deserialized, if those fields are present then its {@code readResolve} + * method will substitute a {@code GenericMBeanException} equivalent to + * this one.

+ */ + Object writeReplace() { + MBeanException x = new MBeanException( + getMessage(), errorCode, userData, getCause()); + x.setStackTrace(this.getStackTrace()); + return x; + } +} diff --git a/src/share/classes/javax/management/MBeanException.java b/src/share/classes/javax/management/MBeanException.java index 839daa5dd..7edc6639a 100644 --- a/src/share/classes/javax/management/MBeanException.java +++ b/src/share/classes/javax/management/MBeanException.java @@ -25,6 +25,8 @@ package javax.management; +import javax.management.openmbean.CompositeData; + /** * Represents "user defined" exceptions thrown by MBean methods @@ -40,6 +42,26 @@ public class MBeanException extends JMException { /* Serial version */ private static final long serialVersionUID = 4066342430588744142L; + /** + * @serial This field is null for instances of this class that were + * produced by its public constructors. It is non-null for instances + * of this class that represent serialized instances of {@link + * GenericMBeanException}. + * + * @see GenericMBeanException#getErrorCode() + */ + final String errorCode; + + /** + * @serial This field is null for instances of this class that were + * produced by its public constructors. It may be non-null for instances + * of this class that represent serialized instances of {@link + * GenericMBeanException}. + * + * @see GenericMBeanException#getUserData() + */ + final CompositeData userData; + /** * @serial Encapsulated {@link Exception} */ @@ -51,9 +73,8 @@ public class MBeanException extends JMException { * * @param e the wrapped exception. */ - public MBeanException(java.lang.Exception e) { - super() ; - exception = e ; + public MBeanException(Exception e) { + this(null, null, null, e); } /** @@ -63,11 +84,19 @@ public class MBeanException extends JMException { * @param e the wrapped exception. * @param message the detail message. */ - public MBeanException(java.lang.Exception e, String message) { - super(message) ; - exception = e ; + public MBeanException(Exception e, String message) { + this(message, null, null, e); } + MBeanException( + String message, String errorCode, CompositeData userData, Throwable cause) { + super(message); + initCause(cause); + if (cause instanceof Exception) + this.exception = (Exception) cause; + this.errorCode = errorCode; + this.userData = userData; + } /** * Return the actual {@link Exception} thrown. @@ -79,11 +108,24 @@ public class MBeanException extends JMException { } /** - * Return the actual {@link Exception} thrown. - * - * @return the wrapped exception. + * This method is invoked when deserializing instances of this class. + * If the {@code errorCode} field of the deserialized instance is not + * null, this method returns an instance of {@link GenericMBeanException} + * instead. Otherwise it returns {@code this}. + * @return {@code this}, or a {@code GenericMBeanException}. */ - public Throwable getCause() { - return exception; + Object readResolve() { + if (errorCode == null) { + // serial compatibility: earlier versions did not set + // Throwable.cause because they overrode getCause(). + if (getCause() == null && exception != null) + initCause(exception); + return this; + } else { + Throwable t = new GenericMBeanException( + getMessage(), errorCode, userData, getCause()); + t.setStackTrace(this.getStackTrace()); + return t; + } } } diff --git a/test/javax/management/interop/MBeanExceptionInteropTest.java b/test/javax/management/interop/MBeanExceptionInteropTest.java new file mode 100644 index 000000000..dff6d9639 --- /dev/null +++ b/test/javax/management/interop/MBeanExceptionInteropTest.java @@ -0,0 +1,166 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6456269 + * @summary Test that an MBeanException serialized on JDK 6 deserializes + * correctly on JDK 7. + * @author Eamonn McManus + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import javax.management.MBeanException; + +// In JDK 6, the Throwable.cause field was always null for an MBeanException, +// but it didn't matter because we overrode getCause() to return +// MBeanException.exception instead. In JDK 7, we no longer override getCause() +// because MBeanException doubles as the serial form of GenericMBeanException. +// So we need some care to make sure that objects deserialized from JDK 6 +// have the right getCause() behaviour. +public class MBeanExceptionInteropTest { + private static final byte[] SERIALIZED_MBEAN_EXCEPTION = { + -84,-19,0,5,115,114,0,31,106,97,118,97,120,46,109,97, + 110,97,103,101,109,101,110,116,46,77,66,101,97,110,69,120, + 99,101,112,116,105,111,110,56,110,-116,-27,110,87,49,-50,2, + 0,1,76,0,9,101,120,99,101,112,116,105,111,110,116,0, + 21,76,106,97,118,97,47,108,97,110,103,47,69,120,99,101, + 112,116,105,111,110,59,120,114,0,28,106,97,118,97,120,46, + 109,97,110,97,103,101,109,101,110,116,46,74,77,69,120,99, + 101,112,116,105,111,110,4,-35,76,-20,-109,-99,126,113,2,0, + 0,120,114,0,19,106,97,118,97,46,108,97,110,103,46,69, + 120,99,101,112,116,105,111,110,-48,-3,31,62,26,59,28,-60, + 2,0,0,120,114,0,19,106,97,118,97,46,108,97,110,103, + 46,84,104,114,111,119,97,98,108,101,-43,-58,53,39,57,119, + -72,-53,3,0,3,76,0,5,99,97,117,115,101,116,0,21, + 76,106,97,118,97,47,108,97,110,103,47,84,104,114,111,119, + 97,98,108,101,59,76,0,13,100,101,116,97,105,108,77,101, + 115,115,97,103,101,116,0,18,76,106,97,118,97,47,108,97, + 110,103,47,83,116,114,105,110,103,59,91,0,10,115,116,97, + 99,107,84,114,97,99,101,116,0,30,91,76,106,97,118,97, + 47,108,97,110,103,47,83,116,97,99,107,84,114,97,99,101, + 69,108,101,109,101,110,116,59,120,112,113,0,126,0,8,116, + 0,7,79,104,32,100,101,97,114,117,114,0,30,91,76,106, + 97,118,97,46,108,97,110,103,46,83,116,97,99,107,84,114, + 97,99,101,69,108,101,109,101,110,116,59,2,70,42,60,60, + -3,34,57,2,0,0,120,112,0,0,0,2,115,114,0,27, + 106,97,118,97,46,108,97,110,103,46,83,116,97,99,107,84, + 114,97,99,101,69,108,101,109,101,110,116,97,9,-59,-102,38, + 54,-35,-123,2,0,4,73,0,10,108,105,110,101,78,117,109, + 98,101,114,76,0,14,100,101,99,108,97,114,105,110,103,67, + 108,97,115,115,113,0,126,0,6,76,0,8,102,105,108,101, + 78,97,109,101,113,0,126,0,6,76,0,10,109,101,116,104, + 111,100,78,97,109,101,113,0,126,0,6,120,112,0,0,0, + 63,116,0,25,77,66,101,97,110,69,120,99,101,112,116,105, + 111,110,73,110,116,101,114,111,112,84,101,115,116,116,0,30, + 77,66,101,97,110,69,120,99,101,112,116,105,111,110,73,110, + 116,101,114,111,112,84,101,115,116,46,106,97,118,97,116,0, + 5,119,114,105,116,101,115,113,0,126,0,12,0,0,0,46, + 113,0,126,0,14,113,0,126,0,15,116,0,4,109,97,105, + 110,120,115,114,0,34,106,97,118,97,46,108,97,110,103,46, + 73,108,108,101,103,97,108,65,114,103,117,109,101,110,116,69, + 120,99,101,112,116,105,111,110,-75,-119,115,-45,125,102,-113,-68, + 2,0,0,120,114,0,26,106,97,118,97,46,108,97,110,103, + 46,82,117,110,116,105,109,101,69,120,99,101,112,116,105,111, + 110,-98,95,6,71,10,52,-125,-27,2,0,0,120,113,0,126, + 0,3,113,0,126,0,21,116,0,3,66,97,100,117,113,0, + 126,0,10,0,0,0,2,115,113,0,126,0,12,0,0,0, + 62,113,0,126,0,14,113,0,126,0,15,113,0,126,0,16, + 115,113,0,126,0,12,0,0,0,46,113,0,126,0,14,113, + 0,126,0,15,113,0,126,0,18,120, + }; + + private static volatile String failure; + + public static void main(String[] args) throws Exception { + if (args.length > 0) { + if (args[0].equals("write") && args.length == 1) { + write(); + return; + } else { + System.err.println( + "Usage: java MBeanExceptionInteropTest"); + System.err.println( + "or: java MBeanExceptionInteropTest write"); + System.exit(1); + } + } + + // Read the serialized object and check it is correct. + ByteArrayInputStream bin = + new ByteArrayInputStream(SERIALIZED_MBEAN_EXCEPTION); + ObjectInputStream oin = new ObjectInputStream(bin); + MBeanException mbeanEx = (MBeanException) oin.readObject(); + assertEquals("MBeanException message", "Oh dear", mbeanEx.getMessage()); + System.out.println("getCause(): " + mbeanEx.getCause() + "; " + + "getTargetException(): " + mbeanEx.getTargetException()); + for (Throwable t : + new Throwable[] {mbeanEx.getCause(), mbeanEx.getTargetException()}) { + if (!(t instanceof IllegalArgumentException)) + fail("Nested exception not an IllegalArgumentException: " + t); + else + assertEquals("Nested exception message", "Bad", t.getMessage()); + } + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + // Write a file that can be inserted into this source file as the + // contents of the SERIALIZED_MBEAN_EXCEPTION array. Run this program + // on JDK 6 to generate the array, then test on JDK 7. + private static void write() throws Exception { + Exception wrapped = new IllegalArgumentException("Bad"); + MBeanException mbeanEx = new MBeanException(wrapped, "Oh dear"); + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ObjectOutputStream oout = new ObjectOutputStream(bout); + oout.writeObject(mbeanEx); + oout.close(); + byte[] bytes = bout.toByteArray(); + for (int i = 0; i < bytes.length; i++) { + System.out.printf("%d,", bytes[i]); + if (i % 16 == 15) + System.out.println(); + } + if (bytes.length % 16 != 0) + System.out.println(); + } + + private static void assertEquals(String what, Object expect, Object actual) { + boolean equal = (expect == null) ? (actual == null) : expect.equals(actual); + if (equal) + System.out.println("OK: " + what + ": " + expect); + else + fail(what + ": expected " + expect + ", got " + actual); + } + + private static void fail(String why) { + System.out.println("FAIL: " + why); + failure = why; + } +} diff --git a/test/javax/management/openmbean/GenericMBeanExceptionTest.java b/test/javax/management/openmbean/GenericMBeanExceptionTest.java new file mode 100644 index 000000000..fa682d0d0 --- /dev/null +++ b/test/javax/management/openmbean/GenericMBeanExceptionTest.java @@ -0,0 +1,278 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6456269 + * @summary Test GenericMBeanException + * @author Eamonn McManus + */ + +import java.beans.ConstructorProperties; +import java.lang.management.ManagementFactory; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.management.GenericMBeanException; +import javax.management.JMX; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.MXBeanMapping; +import javax.management.openmbean.MXBeanMappingFactory; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +public class GenericMBeanExceptionTest { + private static volatile String failure = null; + + public static interface ThrowerMBean { + public void throwGeneric() throws GenericMBeanException; + public void throwGeneric(Throwable cause) throws GenericMBeanException; + public void throwGeneric(String errorCode) throws GenericMBeanException; + public void throwGeneric(CompositeData userData) throws GenericMBeanException; + public void throwGeneric(String errorCode, CompositeData userData) + throws GenericMBeanException; + public void throwGeneric(String errorCode, CompositeData userData, Throwable cause) + throws GenericMBeanException; + } + + public static class Thrower implements ThrowerMBean { + + public void throwGeneric() throws GenericMBeanException { + throw new GenericMBeanException("Message"); + } + + public void throwGeneric(Throwable cause) throws GenericMBeanException { + throw new GenericMBeanException("Message", cause); + } + + public void throwGeneric(String errorCode) throws GenericMBeanException { + throw new GenericMBeanException("Message", errorCode, null); + } + + public void throwGeneric(CompositeData userData) throws GenericMBeanException { + throw new GenericMBeanException("Message", null, userData); + } + + public void throwGeneric(String errorCode, CompositeData userData) + throws GenericMBeanException { + throw new GenericMBeanException("Message", errorCode, userData); + } + + public void throwGeneric(String errorCode, CompositeData userData, + Throwable cause) throws GenericMBeanException { + throw new GenericMBeanException("Message", errorCode, userData, cause); + } + } + + public static class Payload { + private final int severity; + private final String subsystem; + + @ConstructorProperties({"severity", "subsystem"}) + public Payload(int severity, String subsystem) { + this.severity = severity; + this.subsystem = subsystem; + } + + public int getSeverity() { + return severity; + } + + public String getSubsystem() { + return subsystem; + } + + @Override + public boolean equals(Object x) { + if (!(x instanceof Payload)) + return false; + Payload p = (Payload) x; + return (severity == p.severity && + (subsystem == null) ? + p.subsystem == null : subsystem.equals(p.subsystem)); + } + + @Override + public int hashCode() { + return severity + subsystem.hashCode(); + } + + @Override + public String toString() { + return "Payload{severity: " + severity + ", subsystem: " + subsystem + "}"; + } + } + + public static void main(String[] args) throws Exception { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName name = new ObjectName("test:type=Thrower"); + Thrower thrower = new Thrower(); + mbs.registerMBean(thrower, name); + + if (args.length > 0) { + System.out.println("Attach client now, hit return to exit"); + System.in.read(); + return; + } + + JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); + JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer( + url, null, mbs); + cs.start(); + JMXServiceURL addr = cs.getAddress(); + + JMXConnector cc = JMXConnectorFactory.connect(addr); + MBeanServerConnection mbsc = cc.getMBeanServerConnection(); + + ThrowerMBean throwerProxy = JMX.newMBeanProxy(mbsc, name, ThrowerMBean.class); + + Payload payload = new Payload(5, "modular modulizer"); + MXBeanMapping payloadMapping = MXBeanMappingFactory.DEFAULT.mappingForType( + Payload.class, MXBeanMappingFactory.DEFAULT); + CompositeData userData = (CompositeData) + payloadMapping.toOpenValue(payload); + Throwable cause = new IllegalArgumentException("Badness"); + + Object[][] testCases = { + {}, + {"code1"}, + {userData}, + {"code2", userData}, + {(String) null, userData}, + {"code99", userData, cause}, + {(String) null, userData, cause}, + }; + + for (Object[] testCase : testCases) { + System.out.println("Test case: " + testCaseString(testCase)); + + // Find which ThrowerMBean method it corresponds to + Method testMethod = null; +search: + for (Method m : ThrowerMBean.class.getMethods()) { + Class[] paramTypes = m.getParameterTypes(); + if (paramTypes.length != testCase.length) + continue; + for (int i = 0; i < paramTypes.length; i++) { + if (testCase[i] != null && !paramTypes[i].isInstance(testCase[i])) + continue search; + } + testMethod = m; + } + + if (testMethod == null) { + throw new Exception("TEST ERROR: no method corresponds: " + + testCaseString(testCase)); + } + + try { + testMethod.invoke(throwerProxy, testCase); + fail("Did not throw exception", testCase); + continue; + } catch (InvocationTargetException e) { + Throwable iteCause = e.getCause(); + if (!(iteCause instanceof GenericMBeanException)) { + iteCause.printStackTrace(System.out); + fail("Threw wrong exception " + iteCause, testCase); + continue; + } + GenericMBeanException ge = (GenericMBeanException) iteCause; + if (!ge.getMessage().equals("Message")) + fail("Wrong message: " + ge.getMessage(), testCase); + + Class[] paramTypes = testMethod.getParameterTypes(); + for (int i = 0; i < paramTypes.length; i++) { + Class paramType = paramTypes[i]; + + if (paramType == Throwable.class) { // cause + Throwable geCause = ge.getCause(); + if (!(geCause instanceof IllegalArgumentException)) + fail("Wrong cause: " + geCause, testCase); + else if (!geCause.getMessage().equals("Badness")) + fail("Wrong cause message: " + geCause.getMessage(), testCase); + } else if (paramType == String.class) { // errorCode + String errorCode = ge.getErrorCode(); + String expectedErrorCode = + (testCase[i] == null) ? "" : (String) testCase[i]; + if (!expectedErrorCode.equals(errorCode)) + fail("Wrong error code: " + ge.getErrorCode(), testCase); + } else if (paramType == CompositeData.class) { // userData + CompositeData userData2 = ge.getUserData(); + if (!userData.equals(userData2)) + fail("Wrong userData: " + userData2, testCase); + Payload payload2 = (Payload) payloadMapping.fromOpenValue(userData2); + if (!payload.equals(payload2)) + fail("Wrong payload: " + payload2, testCase); + } else + throw new Exception("TEST ERROR: unknown parameter type: " + paramType); + } + } + } + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + private static String testCaseString(Object[] testCase) { + StringBuilder sb = new StringBuilder("["); + String sep = ""; + for (Object x : testCase) { + sb.append(sep); + String xs = (x instanceof CompositeData) ? + compositeDataString((CompositeData) x) : String.valueOf(x); + sb.append(xs); + sep = ", "; + } + sb.append("]"); + return sb.toString(); + } + + private static String compositeDataString(CompositeData cd) { + StringBuilder sb = new StringBuilder("CompositeData{"); + CompositeType ct = cd.getCompositeType(); + String sep = ""; + for (String key : ct.keySet()) { + sb.append(sep).append(key).append(": ").append(cd.get(key)); + sep = ", "; + } + sb.append("}"); + return sb.toString(); + } + + private static void fail(String why, Object[] testCase) { + fail(testCaseString(testCase) + ": " + why); + } + + private static void fail(String why) { + failure = why; + System.out.println("FAIL: " + why); + } +} -- GitLab