8264124: Update MXBean specification and implementation to extend mapping of CompositeType to records
Reviewed-by: mchung, chegar, alanb
This commit is contained in:
parent
714ae54f91
commit
d84a7e55be
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. 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
|
||||
@ -443,9 +443,11 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
if (gcInfoHack && propertyName.equals("CompositeType"))
|
||||
continue;
|
||||
|
||||
Method old =
|
||||
getterMap.put(decapitalize(propertyName),
|
||||
method);
|
||||
// Don't decapitalize if this is a record component name.
|
||||
// We only decapitalize for getXxxx(), isXxxx(), and setXxxx()
|
||||
String name = c.isRecord() && method.getName().equals(propertyName)
|
||||
? propertyName : decapitalize(propertyName);
|
||||
Method old = getterMap.put(name, method);
|
||||
if (old != null) {
|
||||
final String msg =
|
||||
"Class " + c.getName() + " has method name clash: " +
|
||||
@ -862,6 +864,9 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
{
|
||||
new CompositeBuilderViaFrom(targetClass, itemNames),
|
||||
},
|
||||
{
|
||||
new RecordCompositeBuilder(targetClass, itemNames),
|
||||
},
|
||||
{
|
||||
new CompositeBuilderViaConstructor(targetClass, itemNames),
|
||||
},
|
||||
@ -1139,14 +1144,14 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
/** Builder for when the target class has a constructor that is
|
||||
annotated with {@linkplain ConstructorParameters @ConstructorParameters}
|
||||
or {@code @ConstructorProperties} so we can see the correspondence to getters. */
|
||||
private static final class CompositeBuilderViaConstructor
|
||||
private static class CompositeBuilderViaConstructor
|
||||
extends CompositeBuilder {
|
||||
|
||||
CompositeBuilderViaConstructor(Class<?> targetClass, String[] itemNames) {
|
||||
super(targetClass, itemNames);
|
||||
}
|
||||
|
||||
private String[] getConstPropValues(Constructor<?> ctr) {
|
||||
String[] getConstPropValues(Constructor<?> ctr) {
|
||||
// is constructor annotated by javax.management.ConstructorParameters ?
|
||||
ConstructorParameters ctrProps = ctr.getAnnotation(ConstructorParameters.class);
|
||||
if (ctrProps != null) {
|
||||
@ -1171,8 +1176,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
}
|
||||
|
||||
if (annotatedConstrList.isEmpty())
|
||||
return "no constructor has either @ConstructorParameters " +
|
||||
"or @ConstructorProperties annotation";
|
||||
return reportNoConstructor();
|
||||
|
||||
annotatedConstructors = newList();
|
||||
|
||||
@ -1196,9 +1200,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
// so we can test unambiguity.
|
||||
Set<BitSet> getterIndexSets = newSet();
|
||||
for (Constructor<?> constr : annotatedConstrList) {
|
||||
String annotationName =
|
||||
constr.isAnnotationPresent(ConstructorParameters.class) ?
|
||||
"@ConstructorParameters" : "@ConstructorProperties";
|
||||
String matchingMechanism = matchingMechanism(constr);
|
||||
|
||||
String[] propertyNames = getConstPropValues(constr);
|
||||
|
||||
@ -1206,7 +1208,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
if (paramTypes.length != propertyNames.length) {
|
||||
final String msg =
|
||||
"Number of constructor params does not match " +
|
||||
annotationName + " annotation: " + constr;
|
||||
referenceMechannism(matchingMechanism) +": " + constr;
|
||||
throw new InvalidObjectException(msg);
|
||||
}
|
||||
|
||||
@ -1219,7 +1221,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
String propertyName = propertyNames[i];
|
||||
if (!getterMap.containsKey(propertyName)) {
|
||||
String msg =
|
||||
annotationName + " includes name " + propertyName +
|
||||
matchingMechanism + " includes name " + propertyName +
|
||||
" which does not correspond to a property";
|
||||
for (String getterName : getterMap.keySet()) {
|
||||
if (getterName.equalsIgnoreCase(propertyName)) {
|
||||
@ -1234,7 +1236,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
paramIndexes[getterIndex] = i;
|
||||
if (present.get(getterIndex)) {
|
||||
final String msg =
|
||||
annotationName + " contains property " +
|
||||
matchingMechanism + " contains property " +
|
||||
propertyName + " more than once: " + constr;
|
||||
throw new InvalidObjectException(msg);
|
||||
}
|
||||
@ -1243,7 +1245,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
Type propertyType = getter.getGenericReturnType();
|
||||
if (!propertyType.equals(paramTypes[i])) {
|
||||
final String msg =
|
||||
annotationName + " gives property " + propertyName +
|
||||
matchingMechanism + " gives property " + propertyName +
|
||||
" of type " + propertyType + " for parameter " +
|
||||
" of type " + paramTypes[i] + ": " + constr;
|
||||
throw new InvalidObjectException(msg);
|
||||
@ -1252,10 +1254,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
|
||||
if (!getterIndexSets.add(present)) {
|
||||
final String msg =
|
||||
"More than one constructor has " +
|
||||
"@ConstructorParameters or @ConstructorProperties " +
|
||||
"annotation with this set of names: " +
|
||||
Arrays.toString(propertyNames);
|
||||
reportMultipleConstructorsFoundFor(propertyNames);
|
||||
throw new InvalidObjectException(msg);
|
||||
}
|
||||
|
||||
@ -1292,10 +1291,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
i = u.nextSetBit(i+1))
|
||||
names.add(itemNames[i]);
|
||||
final String msg =
|
||||
"Constructors with @ConstructorParameters or " +
|
||||
"@ConstructorProperties annotation " +
|
||||
"would be ambiguous for these items: " +
|
||||
names;
|
||||
reportConstructorsAmbiguousFor(names);
|
||||
throw new InvalidObjectException(msg);
|
||||
}
|
||||
}
|
||||
@ -1305,7 +1301,41 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
return null; // success!
|
||||
}
|
||||
|
||||
final Object fromCompositeData(CompositeData cd,
|
||||
String reportNoConstructor() {
|
||||
return "no constructor has either @ConstructorParameters " +
|
||||
"or @ConstructorProperties annotation";
|
||||
}
|
||||
|
||||
String matchingMechanism(Constructor<?> constr) {
|
||||
return constr.isAnnotationPresent(ConstructorParameters.class) ?
|
||||
"@ConstructorParameters" : "@ConstructorProperties";
|
||||
}
|
||||
|
||||
String referenceMechannism(String matchingMechanism) {
|
||||
return matchingMechanism + " annotation";
|
||||
}
|
||||
|
||||
String reportMultipleConstructorsFoundFor(String... propertyNames) {
|
||||
return "More than one constructor has " +
|
||||
"@ConstructorParameters or @ConstructorProperties " +
|
||||
"annotation with this set of names: " +
|
||||
Arrays.toString(propertyNames);
|
||||
}
|
||||
|
||||
String reportConstructorsAmbiguousFor(Set<String> names) {
|
||||
return "Constructors with @ConstructorParameters or " +
|
||||
"@ConstructorProperties annotation " +
|
||||
"would be ambiguous for these items: " +
|
||||
names;
|
||||
}
|
||||
|
||||
String reportNoConstructorFoundFor(Set<String> names) {
|
||||
return "No constructor has either @ConstructorParameters " +
|
||||
"or @ConstructorProperties annotation for this set of " +
|
||||
"items: " + names;
|
||||
}
|
||||
|
||||
Object fromCompositeData(CompositeData cd,
|
||||
String[] itemNames,
|
||||
MXBeanMapping[] mappings)
|
||||
throws InvalidObjectException {
|
||||
@ -1330,10 +1360,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
}
|
||||
|
||||
if (max == null) {
|
||||
final String msg =
|
||||
"No constructor has either @ConstructorParameters " +
|
||||
"or @ConstructorProperties annotation for this set of " +
|
||||
"items: " + ct.keySet();
|
||||
final String msg = reportNoConstructorFoundFor(ct.keySet());
|
||||
throw new InvalidObjectException(msg);
|
||||
}
|
||||
|
||||
@ -1379,6 +1406,73 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
private List<Constr> annotatedConstructors;
|
||||
}
|
||||
|
||||
/** Builder for when the target class is a record */
|
||||
private static final class RecordCompositeBuilder
|
||||
extends CompositeBuilderViaConstructor {
|
||||
|
||||
RecordCompositeBuilder(Class<?> targetClass, String[] itemNames) {
|
||||
super(targetClass, itemNames);
|
||||
}
|
||||
|
||||
String[] getConstPropValues(Constructor<?> ctor) {
|
||||
var components = getTargetClass().getRecordComponents();
|
||||
var ptypes = ctor.getGenericParameterTypes();
|
||||
if (components.length != ptypes.length) {
|
||||
return super.getConstPropValues(ctor);
|
||||
}
|
||||
var len = components.length;
|
||||
String[] res = new String[len];
|
||||
for (int i=0; i < len ; i++) {
|
||||
if (!ptypes[i].equals(components[i].getGenericType())) {
|
||||
return super.getConstPropValues(ctor);
|
||||
}
|
||||
res[i] = components[i].getName();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
String applicable(Method[] getters) throws InvalidObjectException {
|
||||
Class<?> targetClass = getTargetClass();
|
||||
if (!targetClass.isRecord())
|
||||
return "class is not a record";
|
||||
|
||||
return super.applicable(getters);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object fromCompositeData(CompositeData cd, String[] itemNames, MXBeanMapping[] mappings)
|
||||
throws InvalidObjectException {
|
||||
return super.fromCompositeData(cd, itemNames, mappings);
|
||||
}
|
||||
|
||||
String reportNoConstructor() {
|
||||
return "canonical constructor for record not found";
|
||||
}
|
||||
|
||||
String matchingMechanism(Constructor<?> constr) {
|
||||
return "canonical constructor";
|
||||
}
|
||||
|
||||
String referenceMechannism(String matchingMechanism) {
|
||||
return matchingMechanism;
|
||||
}
|
||||
|
||||
String reportMultipleConstructorsFoundFor(String... propertyNames) {
|
||||
return "More than one constructor has this set of names: " +
|
||||
Arrays.toString(propertyNames);
|
||||
}
|
||||
|
||||
String reportConstructorsAmbiguousFor(Set<String> names) {
|
||||
return "Constructors would be ambiguous for these items: " +
|
||||
names;
|
||||
}
|
||||
|
||||
String reportNoConstructorFoundFor(Set<String> names) {
|
||||
return "No constructor has this set of " +
|
||||
"items: " + names;
|
||||
}
|
||||
}
|
||||
|
||||
/** Builder for when the target class is an interface and contains
|
||||
no methods other than getters. Then we can make an instance
|
||||
using a dynamic proxy that forwards the getters to the source
|
||||
@ -1504,7 +1598,16 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
|
||||
public static String propertyName(Method m) {
|
||||
String rest = null;
|
||||
String name = m.getName();
|
||||
if (name.startsWith("get"))
|
||||
var c = m.getDeclaringClass();
|
||||
if (c.isRecord()) {
|
||||
for (var rc : c.getRecordComponents()) {
|
||||
if (name.equals(rc.getName())
|
||||
&& m.getReturnType() == rc.getType()) {
|
||||
rest = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (name.startsWith("get"))
|
||||
rest = name.substring(3);
|
||||
else if (name.startsWith("is") && m.getReturnType() == boolean.class)
|
||||
rest = name.substring(2);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. 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
|
||||
@ -30,6 +30,7 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
|
||||
// remaining imports are for Javadoc
|
||||
import java.io.InvalidObjectException;
|
||||
@ -531,6 +532,13 @@ public class MemoryPool
|
||||
<td>{@link TabularData}<br>
|
||||
(see below)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{@linkplain Record Record classes}</th>
|
||||
<td>{@link CompositeType}, if possible<br>
|
||||
(see below)</td>
|
||||
<td>{@link CompositeData}<br>
|
||||
(see below)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">An MXBean interface</th>
|
||||
<td>{@code SimpleType.OBJECTNAME}<br>
|
||||
@ -543,7 +551,8 @@ public class MemoryPool
|
||||
<td>{@link CompositeType},
|
||||
if possible<br>
|
||||
(see below)</td>
|
||||
<td>{@link CompositeData}</td>
|
||||
<td>{@link CompositeData}<br>
|
||||
(see below)</td>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -656,6 +665,60 @@ TabularType tabularType =
|
||||
TabularData} that serializes as {@code TabularDataSupport}.</p>
|
||||
|
||||
|
||||
<h3 id="records">Mappings for Records</h3>
|
||||
|
||||
<p>A {@linkplain Record record} class <em>J</em> can be converted
|
||||
to a {@link CompositeType} if and only if all its
|
||||
{@linkplain Class#getRecordComponents() components} are
|
||||
convertible to open types. Otherwise, it is not convertible.
|
||||
A record that has no components is not convertible.</p>
|
||||
|
||||
<h4 id="record-type-map">Mapping a record class to
|
||||
{@code CompositeType}</h4>
|
||||
|
||||
<p>A record whose components are all convertible to open
|
||||
types, is itself convertible to a {@link CompositeType}.
|
||||
The record class is converted to a {@code CompositeType}
|
||||
as follows.</p>
|
||||
|
||||
<ul>
|
||||
<li>The type name of the {@code CompositeType} is the name
|
||||
of the record class.</li>
|
||||
|
||||
<li>The record getters are the accessors for the
|
||||
{@linkplain RecordComponent record components}.</li>
|
||||
|
||||
<li>For each record component of type <em>T</em>, the item in
|
||||
the {@code CompositeType} has the same name as the record
|
||||
component and its type is <em>opentype(T)</em>, as
|
||||
defined by the <a href="#mapping-rules">type mapping rules</a>
|
||||
above.</li>
|
||||
</ul>
|
||||
|
||||
<h4 id="record-data-map">Mapping an instance of a record class to
|
||||
{@code CompositeData}</h4>
|
||||
|
||||
<p>The mapping from an instance of a record class to a
|
||||
{@link CompositeData} corresponding to the {@code CompositeType}
|
||||
is the same as specified for
|
||||
<a href="#composite-data-map">other types</a>.</p>
|
||||
|
||||
<h4 id="reconstructing-record">Reconstructing an instance of a record class
|
||||
from a {@code CompositeData}</h4>
|
||||
|
||||
<p>A record is reconstructed using its canonical constructor.
|
||||
The canonical constructor doesn't require the presence of
|
||||
{@link ConstructorParameters @javax.management.ConstructorParameters}
|
||||
or {@code @java.beans.ConstructorProperties} annotations. If these
|
||||
annotations are present on the canonical constructor they
|
||||
will be ignored.</p>
|
||||
|
||||
<p>How an instance of a record class <em>J</em> is reconstructed
|
||||
from a {@link CompositeData} is detailed in
|
||||
<a href="#reconstructing">Reconstructing an instance
|
||||
of Java type or record class <em>J</em> from a {@code CompositeData}</a>
|
||||
below.</p>
|
||||
|
||||
<h3 id="mxbean-map">Mappings for MXBean interfaces</h3>
|
||||
|
||||
<p>An MXBean interface, or a type referenced within an MXBean
|
||||
@ -753,6 +816,9 @@ public interface ModuleMXBean {
|
||||
{@code CompositeType} is determined by the <a href="#type-names">
|
||||
type name rules</a> below.</p>
|
||||
|
||||
<h4 id="composite-type-map">Mapping a Java type <em>J</em>
|
||||
to {@link CompositeType}</h4>
|
||||
|
||||
<p>The class is examined for getters using the conventions
|
||||
<a href="#naming-conv">above</a>. (Getters must be public
|
||||
instance methods.) If there are no getters, or if
|
||||
@ -796,6 +862,9 @@ public interface ModuleMXBean {
|
||||
getOwner} and {@code isOwner}, or {@code getOwner} and {@code
|
||||
getowner}) then the type is not convertible.</p>
|
||||
|
||||
<h4 id="composite-data-map" >Mapping from an instance of Java
|
||||
type or record class <em>J</em> to {@code CompositeData}</h4>
|
||||
|
||||
<p>When the Open Type is {@code CompositeType}, the corresponding
|
||||
mapped Java type (<em>opendata(J)</em>) is {@link
|
||||
CompositeData}. The mapping from an instance of <em>J</em> to a
|
||||
@ -809,7 +878,7 @@ public interface ModuleMXBean {
|
||||
Open Data type. Thus, a getter such as</p>
|
||||
|
||||
<blockquote>
|
||||
{@code List<String> getNames()}
|
||||
{@code List<String> getNames()} (or {@code List<String> names()} for a record)
|
||||
</blockquote>
|
||||
|
||||
<p>will have been mapped to an item with name "{@code names}" and
|
||||
@ -825,8 +894,8 @@ public interface ModuleMXBean {
|
||||
CompositeDataSupport}.</p>
|
||||
|
||||
|
||||
<h4>Reconstructing an instance of Java type <em>J</em> from
|
||||
a {@code CompositeData}</h4>
|
||||
<h4 id="reconstructing">Reconstructing an instance of Java type
|
||||
or record class <em>J</em> from a {@code CompositeData}</h4>
|
||||
|
||||
<p>If <em>opendata(J)</em> is {@code CompositeData} for a Java type
|
||||
<em>J</em>, then either an instance of <em>J</em> can be
|
||||
@ -846,6 +915,17 @@ public interface ModuleMXBean {
|
||||
then that method is called to reconstruct an instance of
|
||||
<em>J</em>.</p></li>
|
||||
|
||||
<li><p>Otherwise, if <em>J</em> is a {@link Record} class,
|
||||
and the record canonical constructor is applicable,
|
||||
an instance of <em>J</em> is reconstructed by calling
|
||||
the record canonical constructor.
|
||||
The canonical constructor, if applicable, is called
|
||||
with the appropriate reconstructed items from the
|
||||
{@code CompositeData}. The canonical constructor
|
||||
is <em>applicable</em> if all the properties named
|
||||
by the record components are present in the
|
||||
{@code CompositeData}.</p></li>
|
||||
|
||||
<li><p>Otherwise, if <em>J</em> has at least one public
|
||||
constructor with either {@link javax.management.ConstructorParameters
|
||||
@javax.management.ConstructorParameters} or
|
||||
@ -962,6 +1042,15 @@ public class NamedNumber {
|
||||
</blockquote>
|
||||
</li>
|
||||
|
||||
<li>Record:
|
||||
|
||||
<blockquote>
|
||||
<pre>
|
||||
public record NamedNumber(int number, String name) {}
|
||||
</pre>
|
||||
</blockquote>
|
||||
</li>
|
||||
|
||||
<li>Public constructor with <code>@ConstructorParameters</code> annotation:
|
||||
|
||||
<blockquote>
|
||||
|
634
test/jdk/javax/management/mxbean/RecordsMXBeanTest.java
Normal file
634
test/jdk/javax/management/mxbean/RecordsMXBeanTest.java
Normal file
@ -0,0 +1,634 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.management.Attribute;
|
||||
import javax.management.ConstructorParameters;
|
||||
import javax.management.JMX;
|
||||
import javax.management.MBeanException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.MBeanServerConnection;
|
||||
import javax.management.MBeanServerFactory;
|
||||
import javax.management.NotCompliantMBeanException;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.StandardMBean;
|
||||
import javax.management.openmbean.CompositeData;
|
||||
import javax.management.openmbean.CompositeDataSupport;
|
||||
import javax.management.openmbean.CompositeDataView;
|
||||
import javax.management.openmbean.CompositeType;
|
||||
import javax.management.openmbean.OpenDataException;
|
||||
import javax.management.openmbean.OpenType;
|
||||
import javax.management.remote.JMXConnector;
|
||||
import javax.management.remote.JMXConnectorServer;
|
||||
import javax.management.remote.JMXConnectorServerFactory;
|
||||
import javax.management.remote.JMXServiceURL;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8264124
|
||||
* @run testng RecordsMXBeanTest
|
||||
*/
|
||||
public class RecordsMXBeanTest {
|
||||
// Simple record with open types
|
||||
public record Data(List<Integer> ints, Map<String, List<String>> map) {}
|
||||
// Used to test case in component names
|
||||
public record MixedCases(int Foo, int BarBar, int foo) {}
|
||||
// Used to test nested records
|
||||
public record DataPoint(Data x, Data y, MixedCases mixed) {}
|
||||
// Used to test reconstruction using a non-canonical constructor
|
||||
public record Annotated(int x, int y, int z) {
|
||||
@ConstructorParameters(value = {"y", "x"})
|
||||
public Annotated(int y, int x) {
|
||||
this(x,y,-1);
|
||||
}
|
||||
}
|
||||
// Used to test reconstruction using a static `from` method
|
||||
public record FromMethod(int x, int y, int z) {
|
||||
public static FromMethod from(CompositeData cd) {
|
||||
int x = (int) cd.get("x");
|
||||
int y = (int) cd.get("y");
|
||||
int z = -x -y;
|
||||
return new FromMethod(x, y, z);
|
||||
}
|
||||
}
|
||||
// A record that exposes methods that look like
|
||||
// getters... These should be ignored - only the
|
||||
// record components should be considered.
|
||||
public record Trickster(int x, int y) {
|
||||
public int getZ() { return -x() -y(); }
|
||||
public boolean isTricky() { return true; }
|
||||
}
|
||||
// A regular class similar to the Trickster,
|
||||
// but this time z and tricky should appear
|
||||
// in the composite data
|
||||
public static class TricksterToo {
|
||||
final int x;
|
||||
final int y;
|
||||
@ConstructorParameters({"x", "y"})
|
||||
public TricksterToo(int x, int y) {
|
||||
this.x = x; this.y = y;
|
||||
}
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getZ() { return -x -y; }
|
||||
public boolean isTricky() { return true; }
|
||||
}
|
||||
// A record with a conflicting name getX/x which
|
||||
// should ensure that non component getters are ignored
|
||||
public record RWithGetter(int x, int y) {
|
||||
public int getX() { return x;}
|
||||
}
|
||||
// A record with an annotated cannonical constructor.
|
||||
// Annotation should be ignored
|
||||
public record WithAnno(int x, int y) {
|
||||
@ConstructorParameters({"y", "x"})
|
||||
public WithAnno(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
// A record that implements CompositeDataView
|
||||
public record WithCDV(int x, int y) implements CompositeDataView {
|
||||
@Override
|
||||
public CompositeData toCompositeData(CompositeType ct) {
|
||||
if (ct == null) return null;
|
||||
try {
|
||||
return new CompositeDataSupport(ct, new String[]{"x", "y"}, new Object[]{x() + 1, y() + 2});
|
||||
} catch (OpenDataException x) {
|
||||
throw new IllegalArgumentException(ct.getTypeName(), x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A read only MXBean interface
|
||||
public interface RecordsMXBean {
|
||||
public Data getData();
|
||||
public DataPoint getDataPoint();
|
||||
public default Map<String, DataPoint> allPoints() {
|
||||
return Map.of("allpoints", getDataPoint());
|
||||
}
|
||||
}
|
||||
|
||||
// A read-write MXBean interface
|
||||
public interface Records2MXBean extends RecordsMXBean {
|
||||
public void setDataPoint(DataPoint point);
|
||||
}
|
||||
|
||||
// An implementation of the read-only MXBean interface which is
|
||||
// itself a record (this is already supported)
|
||||
public record Records(DataPoint point) implements RecordsMXBean {
|
||||
@Override
|
||||
public Data getData() {
|
||||
return point().x();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataPoint getDataPoint() {
|
||||
return point();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, DataPoint> allPoints() {
|
||||
return Map.of("point", point());
|
||||
}
|
||||
}
|
||||
|
||||
// An implementation of the read-write MXBean interface
|
||||
public static class Records2 implements Records2MXBean {
|
||||
private volatile DataPoint point = new DataPoint(
|
||||
new Data(List.of(1, 2), Map.of("foo", List.of("bar"))),
|
||||
new Data(List.of(3, 4), Map.of("bar", List.of("foo"))),
|
||||
new MixedCases(5, 6, 7)
|
||||
);
|
||||
|
||||
@Override
|
||||
public Data getData() {
|
||||
return point.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataPoint getDataPoint() {
|
||||
return point;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDataPoint(DataPoint point) {
|
||||
this.point = point;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, DataPoint> allPoints() {
|
||||
return Map.of("point", point);
|
||||
}
|
||||
}
|
||||
|
||||
// A complex MXBean interface used to test reconstruction
|
||||
// of records through non-canonical annotated constructors
|
||||
// and static `from` method
|
||||
public interface ComplexMXBean {
|
||||
Annotated getAnnotated();
|
||||
void setAnnotated(Annotated annotated);
|
||||
FromMethod getFromMethod();
|
||||
void setFromMethod(FromMethod fromMethod);
|
||||
Trickster getTrickster();
|
||||
void setTrickster(Trickster trick);
|
||||
TricksterToo getTricksterToo();
|
||||
void setTricksterToo(TricksterToo trick);
|
||||
RWithGetter getR();
|
||||
void setR(RWithGetter r);
|
||||
WithAnno getWithAnno();
|
||||
void setWithAnno(WithAnno r);
|
||||
WithCDV getCDV();
|
||||
void setCDV(WithCDV cdv);
|
||||
}
|
||||
|
||||
// An implementation of the complex MXBean interface
|
||||
public static class Complex implements ComplexMXBean {
|
||||
private volatile Annotated annotated = new Annotated(1, 2, 3);
|
||||
private volatile FromMethod fromMethod = new FromMethod(1, 2, 3);
|
||||
private volatile Trickster trickster = new Trickster(4, 5);
|
||||
private volatile TricksterToo too = new TricksterToo(6, 7);
|
||||
private volatile RWithGetter r = new RWithGetter(8, 9);
|
||||
private volatile WithAnno withAnno = new WithAnno(10, 11);
|
||||
private volatile WithCDV withCDV = new WithCDV(12, 13);
|
||||
|
||||
@Override
|
||||
public Annotated getAnnotated() {
|
||||
return annotated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnnotated(Annotated annotated) {
|
||||
this.annotated = annotated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FromMethod getFromMethod() {
|
||||
return fromMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFromMethod(FromMethod fromMethod) {
|
||||
this.fromMethod = fromMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trickster getTrickster() {
|
||||
return trickster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrickster(Trickster trickster) {
|
||||
this.trickster = trickster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TricksterToo getTricksterToo() {
|
||||
return too;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTricksterToo(TricksterToo trick) {
|
||||
too = trick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RWithGetter getR() {
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setR(RWithGetter r) {
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithAnno getWithAnno() {
|
||||
return withAnno;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWithAnno(WithAnno r) {
|
||||
this.withAnno = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithCDV getCDV() {
|
||||
return withCDV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCDV(WithCDV cdv) {
|
||||
withCDV = cdv;
|
||||
}
|
||||
}
|
||||
|
||||
public record NonCompliantR1(int x, Object y) {
|
||||
public int getX() { return x;}
|
||||
}
|
||||
public interface NC1MXBean {
|
||||
public NonCompliantR1 getNCR1();
|
||||
}
|
||||
public class NC1 implements NC1MXBean {
|
||||
private volatile NonCompliantR1 ncr1 = new NonCompliantR1(1,2);
|
||||
|
||||
@Override
|
||||
public NonCompliantR1 getNCR1() {
|
||||
return ncr1;
|
||||
}
|
||||
}
|
||||
|
||||
public record NonCompliantR2(int x, List<? super Integer> y) {
|
||||
}
|
||||
public interface NC2MXBean {
|
||||
public NonCompliantR2 getNCR2();
|
||||
}
|
||||
public class NC2 implements NC2MXBean {
|
||||
private volatile NonCompliantR2 ncr2 = new NonCompliantR2(1,List.of(2));
|
||||
|
||||
@Override
|
||||
public NonCompliantR2 getNCR2() {
|
||||
return ncr2;
|
||||
}
|
||||
}
|
||||
|
||||
public record NonCompliantR3() {
|
||||
}
|
||||
public interface NC3MXBean {
|
||||
public NonCompliantR3 getNCR3();
|
||||
}
|
||||
public class NC3 implements NC3MXBean {
|
||||
private volatile NonCompliantR3 ncr3 = new NonCompliantR3();
|
||||
|
||||
@Override
|
||||
public NonCompliantR3 getNCR3() {
|
||||
return ncr3;
|
||||
}
|
||||
}
|
||||
|
||||
@DataProvider(name = "wrapInStandardMBean")
|
||||
Object[][] wrapInStandardMBean() {
|
||||
return new Object[][] {
|
||||
new Object[] {"wrapped in StandardMBean", true},
|
||||
new Object[] {"not wrapped in StandardMBean", false}
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "wrapInStandardMBean")
|
||||
public void testLocal(String desc, boolean standard) throws Exception {
|
||||
// test local
|
||||
System.out.println("\nTest local " + desc);
|
||||
MBeanServer mbs = MBeanServerFactory.newMBeanServer("test");
|
||||
test(mbs, mbs, standard);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "wrapInStandardMBean")
|
||||
public void testRemote(String desc, boolean standard) throws Exception {
|
||||
// test remote
|
||||
System.out.println("\nTest remote " + desc);
|
||||
MBeanServer mbs = MBeanServerFactory.newMBeanServer("test");
|
||||
final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
|
||||
JMXConnectorServer server =
|
||||
JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
|
||||
server.start();
|
||||
try {
|
||||
JMXConnector ctor = server.toJMXConnector(null);
|
||||
ctor.connect();
|
||||
try {
|
||||
test(mbs, ctor.getMBeanServerConnection(), standard);
|
||||
} finally {
|
||||
ctor.close();
|
||||
}
|
||||
} finally {
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void test(MBeanServer server, MBeanServerConnection connection, boolean standard)
|
||||
throws Exception {
|
||||
|
||||
// test RecordsMXBean via MBeanServerConnection
|
||||
assertTrue(JMX.isMXBeanInterface(RecordsMXBean.class));
|
||||
Records records = new Records(new DataPoint(
|
||||
new Data(List.of(1, 2), Map.of("foo", List.of("bar"))),
|
||||
new Data(List.of(3, 4), Map.of("bar", List.of("foo"))),
|
||||
new MixedCases(5, 6, 7)
|
||||
));
|
||||
ObjectName recname = new ObjectName("test:type=Records");
|
||||
var mbean = standard
|
||||
? new StandardMBean(records, RecordsMXBean.class, true)
|
||||
: records;
|
||||
server.registerMBean(mbean, recname);
|
||||
RecordsMXBean mxBean = JMX.newMXBeanProxy(connection, recname, RecordsMXBean.class);
|
||||
Records retrieved = new Records(mxBean.getDataPoint());
|
||||
assertEquals(retrieved, records);
|
||||
assertEquals(mxBean.allPoints(), records.allPoints());
|
||||
|
||||
// test Records2MXBean via MBeanServerConnection
|
||||
assertTrue(JMX.isMXBeanInterface(Records2MXBean.class));
|
||||
Records2 records2 = new Records2();
|
||||
assertEquals(records2.allPoints(), records.allPoints());
|
||||
ObjectName recname2 = new ObjectName("test:type=Records2");
|
||||
var mbean2 = standard
|
||||
? new StandardMBean(records2, Records2MXBean.class, true)
|
||||
: records2;
|
||||
server.registerMBean(mbean2, recname2);
|
||||
Records2MXBean mxBean2 = JMX.newMXBeanProxy(connection, recname2, Records2MXBean.class);
|
||||
Records retrieved2 = new Records(mxBean2.getDataPoint());
|
||||
assertEquals(retrieved2, records);
|
||||
assertEquals(mxBean2.allPoints(), records.allPoints());
|
||||
|
||||
// mutate Records2MXBean via MBeanServerConnection
|
||||
DataPoint point2 = new DataPoint(records.point().y(), records.point().x(), records.point().mixed());
|
||||
mxBean2.setDataPoint(point2);
|
||||
assertEquals(mxBean2.getDataPoint(), point2);
|
||||
assertEquals(mxBean2.allPoints(), Map.of("point", point2));
|
||||
|
||||
// test reconstruction through non-canonical constructor and from method
|
||||
Complex complex = new Complex();
|
||||
var complexMBean = new StandardMBean(complex, ComplexMXBean.class, true);
|
||||
ObjectName recname3 = new ObjectName("test:type=Complex");
|
||||
var mbean3 = standard ? complexMBean : complex;
|
||||
server.registerMBean(complexMBean, recname3);
|
||||
ComplexMXBean mBean5 = JMX.newMXBeanProxy(connection, recname3, ComplexMXBean.class);
|
||||
var annotated = mBean5.getAnnotated();
|
||||
assertEquals(annotated, complex.getAnnotated());
|
||||
// Obtain the CompositeData that corresponds to the Annotated record
|
||||
var cd = (CompositeData) complexMBean.getAttribute("Annotated");
|
||||
var ct = cd.getCompositeType();
|
||||
// Construct a version of the "Annotated" composite data where z is missing
|
||||
var nct = new CompositeType(ct.getTypeName(), ct.getDescription(), new String[] {"x", "y"},
|
||||
new String[] {ct.getDescription("x"), ct.getDescription("y")},
|
||||
new OpenType<?>[] {ct.getType("x"), ct.getType("y")});
|
||||
var ncd = new CompositeDataSupport(nct, new String[] {"x", "y"},
|
||||
new Object[] {cd.get("x"), cd.get("y")});
|
||||
// send the modified composite data to remote, and check
|
||||
// that the non-canonical constructor was called (this constructor
|
||||
// sets z = -1)
|
||||
connection.setAttribute(recname3, new Attribute("Annotated", ncd));
|
||||
var annotated2 = mBean5.getAnnotated();
|
||||
assertEquals(annotated2.x(), annotated.x());
|
||||
assertEquals(annotated2.y(), annotated2.y());
|
||||
assertEquals(annotated2.z(), -1);
|
||||
// gets the FromMethod record, and check that the `from` method
|
||||
// we defined was called. When reconstructed from our `from` method,
|
||||
// z will be set to z = -x -y;
|
||||
var from = mBean5.getFromMethod();
|
||||
assertEquals(from.x(), 1);
|
||||
assertEquals(from.y(), 2);
|
||||
assertEquals(from.z(), -3);
|
||||
mBean5.setFromMethod(new FromMethod(2, 1, 3));
|
||||
from = mBean5.getFromMethod();
|
||||
assertEquals(from.x(), 2);
|
||||
assertEquals(from.y(), 1);
|
||||
assertEquals(from.z(), -3);
|
||||
// checks that the presence of getter-like methods doesn't
|
||||
// prevent the record from being reconstructed.
|
||||
var cdtrick = (CompositeData) connection.getAttribute(recname3, "Trickster");
|
||||
println("tricky", cdtrick);
|
||||
assertEquals(cdtrick.getCompositeType().keySet(), Set.of("x", "y"));
|
||||
var trick = mBean5.getTrickster();
|
||||
assertEquals(trick.x(), 4);
|
||||
assertEquals(trick.y(), 5);
|
||||
assertEquals(trick.getZ(), -9);
|
||||
assertTrue(trick.isTricky());
|
||||
mBean5.setTrickster(new Trickster(5, 4));
|
||||
trick = mBean5.getTrickster();
|
||||
assertEquals(trick.x(), 5);
|
||||
assertEquals(trick.y(), 4);
|
||||
assertEquals(trick.getZ(), -9);
|
||||
assertTrue(trick.isTricky());
|
||||
// get the "TricksterToo" composite data
|
||||
var cdtoo = (CompositeData) connection.getAttribute(recname3, "TricksterToo");
|
||||
println("tricky too", cdtoo);
|
||||
assertEquals(cdtoo.getCompositeType().keySet(), Set.of("x", "y", "tricky", "z"));
|
||||
var too = mBean5.getTricksterToo();
|
||||
assertEquals(too.getX(), 6);
|
||||
assertEquals(too.getY(), 7);
|
||||
assertEquals(too.getZ(), -13);
|
||||
assertTrue(too.isTricky());
|
||||
mBean5.setTricksterToo(new TricksterToo(7, 6));
|
||||
too = mBean5.getTricksterToo();
|
||||
assertEquals(too.getX(), 7);
|
||||
assertEquals(too.getY(), 6);
|
||||
assertEquals(too.getZ(), -13);
|
||||
assertTrue(too.isTricky());
|
||||
|
||||
// builds a composite data that contains more fields than
|
||||
// the record...
|
||||
var cdtype = cdtrick.getCompositeType();
|
||||
var itemNames = List.of("x", "y", "z", "tricky").toArray(new String[0]);
|
||||
var itemDesc = Stream.of(itemNames)
|
||||
.map(cdtoo.getCompositeType()::getDescription)
|
||||
.toArray(String[]::new);
|
||||
var itemTypes = Stream.of(itemNames)
|
||||
.map(cdtoo.getCompositeType()::getType)
|
||||
.toArray(OpenType<?>[]::new);
|
||||
var cdtype2 = new CompositeType(cdtype.getTypeName(),
|
||||
cdtype.getDescription(), itemNames, itemDesc, itemTypes);
|
||||
var values = Stream.of(itemNames).map(cdtoo::get).toArray();
|
||||
var cdtrick2 = new CompositeDataSupport(cdtype2, itemNames, values);
|
||||
// sets the composite data with more fields - the superfluous fields
|
||||
// should be ignored...
|
||||
connection.setAttribute(recname3, new Attribute("Trickster", cdtrick2));
|
||||
// get the composite data we just set
|
||||
var cdtrick3 = (CompositeData) connection.getAttribute(recname3, "Trickster");
|
||||
assertEquals(cdtrick3.getCompositeType().keySet(), Set.of("x", "y"));
|
||||
// get the "Trickster" through the MXBean proxy
|
||||
var trick3 = mBean5.getTrickster();
|
||||
assertEquals(trick3.x(), 6);
|
||||
assertEquals(trick3.y(), 7);
|
||||
assertEquals(trick3.getZ(), -13);
|
||||
assertEquals(trick3.isTricky(), true);
|
||||
// get record that has both x() and getX()
|
||||
var rWithGetter = mBean5.getR();
|
||||
assertEquals(rWithGetter.x(), rWithGetter.getX());
|
||||
assertEquals(rWithGetter.x(), 8);
|
||||
assertEquals(rWithGetter.y(), 9);
|
||||
mBean5.setR(new RWithGetter(rWithGetter.y(), rWithGetter.x()));
|
||||
rWithGetter = mBean5.getR();
|
||||
assertEquals(rWithGetter.x(), rWithGetter.getX());
|
||||
assertEquals(rWithGetter.x(), 9);
|
||||
assertEquals(rWithGetter.y(), 8);
|
||||
|
||||
var withAnno = mBean5.getWithAnno();
|
||||
assertEquals(withAnno.x(), 10);
|
||||
assertEquals(withAnno.y(), 11);
|
||||
withAnno = new WithAnno(12, 13);
|
||||
mBean5.setWithAnno(withAnno);
|
||||
withAnno = mBean5.getWithAnno();
|
||||
assertEquals(withAnno.x(), 12);
|
||||
assertEquals(withAnno.y(), 13);
|
||||
|
||||
// WithCDV.toCompositeData adds 1 to x and 2 to y,
|
||||
// we can check how many time it's been called
|
||||
// by looking at the values for x and y.
|
||||
var cdv = mBean5.getCDV();
|
||||
assertEquals(cdv.x(), 13 /* 12 + 1 */, "x");
|
||||
assertEquals(cdv.y(), 15 /* 13 + 2 */, "y");
|
||||
mBean5.setCDV(new WithCDV(14, 15));
|
||||
cdv = mBean5.getCDV();
|
||||
assertEquals(cdv.x(), 16 /* 14 + 1*2 */, "x");
|
||||
assertEquals(cdv.y(), 19 /* 15 + 2*2 */, "y");
|
||||
|
||||
// Test non compliant records: this one has an Object (not mappable to OpenType)
|
||||
var recname4 = new ObjectName("test:type=NCR1");
|
||||
var x = standard
|
||||
? expectThrows(IllegalArgumentException.class,
|
||||
() -> new StandardMBean(new NC1(), NC1MXBean.class, true))
|
||||
: expectThrows(NotCompliantMBeanException.class,
|
||||
() -> server.registerMBean(new NC1(), recname4));
|
||||
reportExpected(x);
|
||||
assertEquals( originalCause(x).getClass(), OpenDataException.class);
|
||||
|
||||
// Test non compliant records: this one has a List<? super Integer>
|
||||
// (not mappable to OpenType)
|
||||
var recname5 = new ObjectName("test:type=NCR2");
|
||||
var x2 = standard
|
||||
? expectThrows(IllegalArgumentException.class,
|
||||
() -> new StandardMBean(new NC2(), NC2MXBean.class, true))
|
||||
: expectThrows(NotCompliantMBeanException.class,
|
||||
() -> server.registerMBean(new NC2(), recname5));
|
||||
reportExpected(x2);
|
||||
assertEquals( originalCause(x2).getClass(), OpenDataException.class);
|
||||
|
||||
// Test non compliant records: this one has no getters
|
||||
// (not mappable to OpenType)
|
||||
var recname6 = new ObjectName("test:type=NCR3");
|
||||
var x3 = standard
|
||||
? expectThrows(IllegalArgumentException.class,
|
||||
() -> new StandardMBean(new NC3(), NC3MXBean.class, true))
|
||||
: expectThrows(NotCompliantMBeanException.class,
|
||||
() -> server.registerMBean(new NC3(), recname6));
|
||||
reportExpected(x3);
|
||||
assertEquals( originalCause(x3).getClass(), OpenDataException.class);
|
||||
|
||||
// test that a composite data that doesn't have all the records
|
||||
// components prevents the record from being reconstructed.
|
||||
var recname7 = new ObjectName("test:type=Records2,instance=6");
|
||||
Records2 rec2 = new Records2();
|
||||
var mbean7 = standard
|
||||
? new StandardMBean(rec2, Records2MXBean.class, true)
|
||||
: rec2;
|
||||
server.registerMBean(mbean7, recname7);
|
||||
var cd7 = (CompositeData) server.getAttribute(recname7, "DataPoint");
|
||||
var cdt7 = cd7.getCompositeType();
|
||||
var itemNames7 = List.of("x", "mixed")
|
||||
.toArray(String[]::new);
|
||||
var itemDesc7 = Stream.of(itemNames7)
|
||||
.map(cdt7::getDescription)
|
||||
.toArray(String[]::new);
|
||||
var itemTypes7 = Stream.of(itemNames7)
|
||||
.map(cdt7::getType)
|
||||
.toArray(OpenType<?>[]::new);
|
||||
var notmappable = new CompositeType(cdt7.getTypeName(),
|
||||
cdt7.getDescription(),
|
||||
itemNames7,
|
||||
itemDesc7,
|
||||
itemTypes7);
|
||||
var itemValues7 = Stream.of(itemNames7)
|
||||
.map(cd7::get)
|
||||
.toArray();
|
||||
var notmappableVal = new CompositeDataSupport(notmappable, itemNames7, itemValues7);
|
||||
var attribute6 = new Attribute("DataPoint", notmappableVal);
|
||||
var x4 = expectThrows(MBeanException.class,
|
||||
standard ? () -> ((StandardMBean)mbean7).setAttribute(attribute6)
|
||||
: () -> server.setAttribute(recname7, attribute6));
|
||||
reportExpected(x4);
|
||||
assertEquals(originalCause(x4).getClass(), InvalidObjectException.class);
|
||||
|
||||
}
|
||||
|
||||
static final void reportExpected(Throwable x) {
|
||||
System.out.println("\nGot expected exception: " + x);
|
||||
Throwable cause = x;
|
||||
while ((cause = cause.getCause()) != null) {
|
||||
System.out.println("\tCaused by: " + cause);
|
||||
}
|
||||
}
|
||||
|
||||
static final Throwable originalCause(Throwable t) {
|
||||
while (t.getCause() != null) t = t.getCause();
|
||||
return t;
|
||||
}
|
||||
|
||||
static void println(String name, CompositeData cd) {
|
||||
var cdt = cd.getCompositeType();
|
||||
System.out.printf("%s: %s %s\n", name, cdt.getTypeName(),
|
||||
cdt.keySet().stream()
|
||||
.map(k -> k + "=" + cd.get(k))
|
||||
.collect(Collectors.joining(", ", "{ ", " }")));
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user