Path: blob/master/test/jdk/javax/management/mxbean/RecordsMXBeanTest.java
41149 views
/*1* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.io.InvalidObjectException;24import java.util.Collection;25import java.util.List;26import java.util.Map;27import java.util.Set;28import java.util.stream.Collectors;29import java.util.stream.Stream;30import javax.management.Attribute;31import javax.management.ConstructorParameters;32import javax.management.JMX;33import javax.management.MBeanException;34import javax.management.MBeanServer;35import javax.management.MBeanServerConnection;36import javax.management.MBeanServerFactory;37import javax.management.NotCompliantMBeanException;38import javax.management.ObjectName;39import javax.management.StandardMBean;40import javax.management.openmbean.CompositeData;41import javax.management.openmbean.CompositeDataSupport;42import javax.management.openmbean.CompositeDataView;43import javax.management.openmbean.CompositeType;44import javax.management.openmbean.OpenDataException;45import javax.management.openmbean.OpenType;46import javax.management.remote.JMXConnector;47import javax.management.remote.JMXConnectorServer;48import javax.management.remote.JMXConnectorServerFactory;49import javax.management.remote.JMXServiceURL;5051import org.testng.annotations.DataProvider;52import org.testng.annotations.Test;53import static org.testng.Assert.*;5455/**56* @test57* @bug 826412458* @run testng RecordsMXBeanTest59*/60public class RecordsMXBeanTest {61// Simple record with open types62public record Data(List<Integer> ints, Map<String, List<String>> map) {}63// Used to test case in component names64public record MixedCases(int Foo, int BarBar, int foo) {}65// Used to test nested records66public record DataPoint(Data x, Data y, MixedCases mixed) {}67// Used to test reconstruction using a non-canonical constructor68public record Annotated(int x, int y, int z) {69@ConstructorParameters(value = {"y", "x"})70public Annotated(int y, int x) {71this(x,y,-1);72}73}74// Used to test reconstruction using a static `from` method75public record FromMethod(int x, int y, int z) {76public static FromMethod from(CompositeData cd) {77int x = (int) cd.get("x");78int y = (int) cd.get("y");79int z = -x -y;80return new FromMethod(x, y, z);81}82}83// A record that exposes methods that look like84// getters... These should be ignored - only the85// record components should be considered.86public record Trickster(int x, int y) {87public int getZ() { return -x() -y(); }88public boolean isTricky() { return true; }89}90// A regular class similar to the Trickster,91// but this time z and tricky should appear92// in the composite data93public static class TricksterToo {94final int x;95final int y;96@ConstructorParameters({"x", "y"})97public TricksterToo(int x, int y) {98this.x = x; this.y = y;99}100public int getX() { return x; }101public int getY() { return y; }102public int getZ() { return -x -y; }103public boolean isTricky() { return true; }104}105// A record with a conflicting name getX/x which106// should ensure that non component getters are ignored107public record RWithGetter(int x, int y) {108public int getX() { return x;}109}110// A record with an annotated cannonical constructor.111// Annotation should be ignored112public record WithAnno(int x, int y) {113@ConstructorParameters({"y", "x"})114public WithAnno(int x, int y) {115this.x = x;116this.y = y;117}118}119// A record that implements CompositeDataView120public record WithCDV(int x, int y) implements CompositeDataView {121@Override122public CompositeData toCompositeData(CompositeType ct) {123if (ct == null) return null;124try {125return new CompositeDataSupport(ct, new String[]{"x", "y"}, new Object[]{x() + 1, y() + 2});126} catch (OpenDataException x) {127throw new IllegalArgumentException(ct.getTypeName(), x);128}129}130}131132// A read only MXBean interface133public interface RecordsMXBean {134public Data getData();135public DataPoint getDataPoint();136public default Map<String, DataPoint> allPoints() {137return Map.of("allpoints", getDataPoint());138}139}140141// A read-write MXBean interface142public interface Records2MXBean extends RecordsMXBean {143public void setDataPoint(DataPoint point);144}145146// An implementation of the read-only MXBean interface which is147// itself a record (this is already supported)148public record Records(DataPoint point) implements RecordsMXBean {149@Override150public Data getData() {151return point().x();152}153154@Override155public DataPoint getDataPoint() {156return point();157}158159@Override160public Map<String, DataPoint> allPoints() {161return Map.of("point", point());162}163}164165// An implementation of the read-write MXBean interface166public static class Records2 implements Records2MXBean {167private volatile DataPoint point = new DataPoint(168new Data(List.of(1, 2), Map.of("foo", List.of("bar"))),169new Data(List.of(3, 4), Map.of("bar", List.of("foo"))),170new MixedCases(5, 6, 7)171);172173@Override174public Data getData() {175return point.x;176}177178@Override179public DataPoint getDataPoint() {180return point;181}182183@Override184public void setDataPoint(DataPoint point) {185this.point = point;186}187188@Override189public Map<String, DataPoint> allPoints() {190return Map.of("point", point);191}192}193194// A complex MXBean interface used to test reconstruction195// of records through non-canonical annotated constructors196// and static `from` method197public interface ComplexMXBean {198Annotated getAnnotated();199void setAnnotated(Annotated annotated);200FromMethod getFromMethod();201void setFromMethod(FromMethod fromMethod);202Trickster getTrickster();203void setTrickster(Trickster trick);204TricksterToo getTricksterToo();205void setTricksterToo(TricksterToo trick);206RWithGetter getR();207void setR(RWithGetter r);208WithAnno getWithAnno();209void setWithAnno(WithAnno r);210WithCDV getCDV();211void setCDV(WithCDV cdv);212}213214// An implementation of the complex MXBean interface215public static class Complex implements ComplexMXBean {216private volatile Annotated annotated = new Annotated(1, 2, 3);217private volatile FromMethod fromMethod = new FromMethod(1, 2, 3);218private volatile Trickster trickster = new Trickster(4, 5);219private volatile TricksterToo too = new TricksterToo(6, 7);220private volatile RWithGetter r = new RWithGetter(8, 9);221private volatile WithAnno withAnno = new WithAnno(10, 11);222private volatile WithCDV withCDV = new WithCDV(12, 13);223224@Override225public Annotated getAnnotated() {226return annotated;227}228229@Override230public void setAnnotated(Annotated annotated) {231this.annotated = annotated;232}233234@Override235public FromMethod getFromMethod() {236return fromMethod;237}238239@Override240public void setFromMethod(FromMethod fromMethod) {241this.fromMethod = fromMethod;242}243244@Override245public Trickster getTrickster() {246return trickster;247}248249@Override250public void setTrickster(Trickster trickster) {251this.trickster = trickster;252}253254@Override255public TricksterToo getTricksterToo() {256return too;257}258259@Override260public void setTricksterToo(TricksterToo trick) {261too = trick;262}263264@Override265public RWithGetter getR() {266return r;267}268269@Override270public void setR(RWithGetter r) {271this.r = r;272}273274@Override275public WithAnno getWithAnno() {276return withAnno;277}278279@Override280public void setWithAnno(WithAnno r) {281this.withAnno = r;282}283284@Override285public WithCDV getCDV() {286return withCDV;287}288289@Override290public void setCDV(WithCDV cdv) {291withCDV = cdv;292}293}294295public record NonCompliantR1(int x, Object y) {296public int getX() { return x;}297}298public interface NC1MXBean {299public NonCompliantR1 getNCR1();300}301public class NC1 implements NC1MXBean {302private volatile NonCompliantR1 ncr1 = new NonCompliantR1(1,2);303304@Override305public NonCompliantR1 getNCR1() {306return ncr1;307}308}309310public record NonCompliantR2(int x, List<? super Integer> y) {311}312public interface NC2MXBean {313public NonCompliantR2 getNCR2();314}315public class NC2 implements NC2MXBean {316private volatile NonCompliantR2 ncr2 = new NonCompliantR2(1,List.of(2));317318@Override319public NonCompliantR2 getNCR2() {320return ncr2;321}322}323324public record NonCompliantR3() {325}326public interface NC3MXBean {327public NonCompliantR3 getNCR3();328}329public class NC3 implements NC3MXBean {330private volatile NonCompliantR3 ncr3 = new NonCompliantR3();331332@Override333public NonCompliantR3 getNCR3() {334return ncr3;335}336}337338@DataProvider(name = "wrapInStandardMBean")339Object[][] wrapInStandardMBean() {340return new Object[][] {341new Object[] {"wrapped in StandardMBean", true},342new Object[] {"not wrapped in StandardMBean", false}343};344}345346@Test(dataProvider = "wrapInStandardMBean")347public void testLocal(String desc, boolean standard) throws Exception {348// test local349System.out.println("\nTest local " + desc);350MBeanServer mbs = MBeanServerFactory.newMBeanServer("test");351test(mbs, mbs, standard);352}353354@Test(dataProvider = "wrapInStandardMBean")355public void testRemote(String desc, boolean standard) throws Exception {356// test remote357System.out.println("\nTest remote " + desc);358MBeanServer mbs = MBeanServerFactory.newMBeanServer("test");359final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");360JMXConnectorServer server =361JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);362server.start();363try {364JMXConnector ctor = server.toJMXConnector(null);365ctor.connect();366try {367test(mbs, ctor.getMBeanServerConnection(), standard);368} finally {369ctor.close();370}371} finally {372server.stop();373}374}375376private void test(MBeanServer server, MBeanServerConnection connection, boolean standard)377throws Exception {378379// test RecordsMXBean via MBeanServerConnection380assertTrue(JMX.isMXBeanInterface(RecordsMXBean.class));381Records records = new Records(new DataPoint(382new Data(List.of(1, 2), Map.of("foo", List.of("bar"))),383new Data(List.of(3, 4), Map.of("bar", List.of("foo"))),384new MixedCases(5, 6, 7)385));386ObjectName recname = new ObjectName("test:type=Records");387var mbean = standard388? new StandardMBean(records, RecordsMXBean.class, true)389: records;390server.registerMBean(mbean, recname);391RecordsMXBean mxBean = JMX.newMXBeanProxy(connection, recname, RecordsMXBean.class);392Records retrieved = new Records(mxBean.getDataPoint());393assertEquals(retrieved, records);394assertEquals(mxBean.allPoints(), records.allPoints());395396// test Records2MXBean via MBeanServerConnection397assertTrue(JMX.isMXBeanInterface(Records2MXBean.class));398Records2 records2 = new Records2();399assertEquals(records2.allPoints(), records.allPoints());400ObjectName recname2 = new ObjectName("test:type=Records2");401var mbean2 = standard402? new StandardMBean(records2, Records2MXBean.class, true)403: records2;404server.registerMBean(mbean2, recname2);405Records2MXBean mxBean2 = JMX.newMXBeanProxy(connection, recname2, Records2MXBean.class);406Records retrieved2 = new Records(mxBean2.getDataPoint());407assertEquals(retrieved2, records);408assertEquals(mxBean2.allPoints(), records.allPoints());409410// mutate Records2MXBean via MBeanServerConnection411DataPoint point2 = new DataPoint(records.point().y(), records.point().x(), records.point().mixed());412mxBean2.setDataPoint(point2);413assertEquals(mxBean2.getDataPoint(), point2);414assertEquals(mxBean2.allPoints(), Map.of("point", point2));415416// test reconstruction through non-canonical constructor and from method417Complex complex = new Complex();418var complexMBean = new StandardMBean(complex, ComplexMXBean.class, true);419ObjectName recname3 = new ObjectName("test:type=Complex");420var mbean3 = standard ? complexMBean : complex;421server.registerMBean(complexMBean, recname3);422ComplexMXBean mBean5 = JMX.newMXBeanProxy(connection, recname3, ComplexMXBean.class);423var annotated = mBean5.getAnnotated();424assertEquals(annotated, complex.getAnnotated());425// Obtain the CompositeData that corresponds to the Annotated record426var cd = (CompositeData) complexMBean.getAttribute("Annotated");427var ct = cd.getCompositeType();428// Construct a version of the "Annotated" composite data where z is missing429var nct = new CompositeType(ct.getTypeName(), ct.getDescription(), new String[] {"x", "y"},430new String[] {ct.getDescription("x"), ct.getDescription("y")},431new OpenType<?>[] {ct.getType("x"), ct.getType("y")});432var ncd = new CompositeDataSupport(nct, new String[] {"x", "y"},433new Object[] {cd.get("x"), cd.get("y")});434// send the modified composite data to remote, and check435// that the non-canonical constructor was called (this constructor436// sets z = -1)437connection.setAttribute(recname3, new Attribute("Annotated", ncd));438var annotated2 = mBean5.getAnnotated();439assertEquals(annotated2.x(), annotated.x());440assertEquals(annotated2.y(), annotated2.y());441assertEquals(annotated2.z(), -1);442// gets the FromMethod record, and check that the `from` method443// we defined was called. When reconstructed from our `from` method,444// z will be set to z = -x -y;445var from = mBean5.getFromMethod();446assertEquals(from.x(), 1);447assertEquals(from.y(), 2);448assertEquals(from.z(), -3);449mBean5.setFromMethod(new FromMethod(2, 1, 3));450from = mBean5.getFromMethod();451assertEquals(from.x(), 2);452assertEquals(from.y(), 1);453assertEquals(from.z(), -3);454// checks that the presence of getter-like methods doesn't455// prevent the record from being reconstructed.456var cdtrick = (CompositeData) connection.getAttribute(recname3, "Trickster");457println("tricky", cdtrick);458assertEquals(cdtrick.getCompositeType().keySet(), Set.of("x", "y"));459var trick = mBean5.getTrickster();460assertEquals(trick.x(), 4);461assertEquals(trick.y(), 5);462assertEquals(trick.getZ(), -9);463assertTrue(trick.isTricky());464mBean5.setTrickster(new Trickster(5, 4));465trick = mBean5.getTrickster();466assertEquals(trick.x(), 5);467assertEquals(trick.y(), 4);468assertEquals(trick.getZ(), -9);469assertTrue(trick.isTricky());470// get the "TricksterToo" composite data471var cdtoo = (CompositeData) connection.getAttribute(recname3, "TricksterToo");472println("tricky too", cdtoo);473assertEquals(cdtoo.getCompositeType().keySet(), Set.of("x", "y", "tricky", "z"));474var too = mBean5.getTricksterToo();475assertEquals(too.getX(), 6);476assertEquals(too.getY(), 7);477assertEquals(too.getZ(), -13);478assertTrue(too.isTricky());479mBean5.setTricksterToo(new TricksterToo(7, 6));480too = mBean5.getTricksterToo();481assertEquals(too.getX(), 7);482assertEquals(too.getY(), 6);483assertEquals(too.getZ(), -13);484assertTrue(too.isTricky());485486// builds a composite data that contains more fields than487// the record...488var cdtype = cdtrick.getCompositeType();489var itemNames = List.of("x", "y", "z", "tricky").toArray(new String[0]);490var itemDesc = Stream.of(itemNames)491.map(cdtoo.getCompositeType()::getDescription)492.toArray(String[]::new);493var itemTypes = Stream.of(itemNames)494.map(cdtoo.getCompositeType()::getType)495.toArray(OpenType<?>[]::new);496var cdtype2 = new CompositeType(cdtype.getTypeName(),497cdtype.getDescription(), itemNames, itemDesc, itemTypes);498var values = Stream.of(itemNames).map(cdtoo::get).toArray();499var cdtrick2 = new CompositeDataSupport(cdtype2, itemNames, values);500// sets the composite data with more fields - the superfluous fields501// should be ignored...502connection.setAttribute(recname3, new Attribute("Trickster", cdtrick2));503// get the composite data we just set504var cdtrick3 = (CompositeData) connection.getAttribute(recname3, "Trickster");505assertEquals(cdtrick3.getCompositeType().keySet(), Set.of("x", "y"));506// get the "Trickster" through the MXBean proxy507var trick3 = mBean5.getTrickster();508assertEquals(trick3.x(), 6);509assertEquals(trick3.y(), 7);510assertEquals(trick3.getZ(), -13);511assertEquals(trick3.isTricky(), true);512// get record that has both x() and getX()513var rWithGetter = mBean5.getR();514assertEquals(rWithGetter.x(), rWithGetter.getX());515assertEquals(rWithGetter.x(), 8);516assertEquals(rWithGetter.y(), 9);517mBean5.setR(new RWithGetter(rWithGetter.y(), rWithGetter.x()));518rWithGetter = mBean5.getR();519assertEquals(rWithGetter.x(), rWithGetter.getX());520assertEquals(rWithGetter.x(), 9);521assertEquals(rWithGetter.y(), 8);522523var withAnno = mBean5.getWithAnno();524assertEquals(withAnno.x(), 10);525assertEquals(withAnno.y(), 11);526withAnno = new WithAnno(12, 13);527mBean5.setWithAnno(withAnno);528withAnno = mBean5.getWithAnno();529assertEquals(withAnno.x(), 12);530assertEquals(withAnno.y(), 13);531532// WithCDV.toCompositeData adds 1 to x and 2 to y,533// we can check how many time it's been called534// by looking at the values for x and y.535var cdv = mBean5.getCDV();536assertEquals(cdv.x(), 13 /* 12 + 1 */, "x");537assertEquals(cdv.y(), 15 /* 13 + 2 */, "y");538mBean5.setCDV(new WithCDV(14, 15));539cdv = mBean5.getCDV();540assertEquals(cdv.x(), 16 /* 14 + 1*2 */, "x");541assertEquals(cdv.y(), 19 /* 15 + 2*2 */, "y");542543// Test non compliant records: this one has an Object (not mappable to OpenType)544var recname4 = new ObjectName("test:type=NCR1");545var x = standard546? expectThrows(IllegalArgumentException.class,547() -> new StandardMBean(new NC1(), NC1MXBean.class, true))548: expectThrows(NotCompliantMBeanException.class,549() -> server.registerMBean(new NC1(), recname4));550reportExpected(x);551assertEquals( originalCause(x).getClass(), OpenDataException.class);552553// Test non compliant records: this one has a List<? super Integer>554// (not mappable to OpenType)555var recname5 = new ObjectName("test:type=NCR2");556var x2 = standard557? expectThrows(IllegalArgumentException.class,558() -> new StandardMBean(new NC2(), NC2MXBean.class, true))559: expectThrows(NotCompliantMBeanException.class,560() -> server.registerMBean(new NC2(), recname5));561reportExpected(x2);562assertEquals( originalCause(x2).getClass(), OpenDataException.class);563564// Test non compliant records: this one has no getters565// (not mappable to OpenType)566var recname6 = new ObjectName("test:type=NCR3");567var x3 = standard568? expectThrows(IllegalArgumentException.class,569() -> new StandardMBean(new NC3(), NC3MXBean.class, true))570: expectThrows(NotCompliantMBeanException.class,571() -> server.registerMBean(new NC3(), recname6));572reportExpected(x3);573assertEquals( originalCause(x3).getClass(), OpenDataException.class);574575// test that a composite data that doesn't have all the records576// components prevents the record from being reconstructed.577var recname7 = new ObjectName("test:type=Records2,instance=6");578Records2 rec2 = new Records2();579var mbean7 = standard580? new StandardMBean(rec2, Records2MXBean.class, true)581: rec2;582server.registerMBean(mbean7, recname7);583var cd7 = (CompositeData) server.getAttribute(recname7, "DataPoint");584var cdt7 = cd7.getCompositeType();585var itemNames7 = List.of("x", "mixed")586.toArray(String[]::new);587var itemDesc7 = Stream.of(itemNames7)588.map(cdt7::getDescription)589.toArray(String[]::new);590var itemTypes7 = Stream.of(itemNames7)591.map(cdt7::getType)592.toArray(OpenType<?>[]::new);593var notmappable = new CompositeType(cdt7.getTypeName(),594cdt7.getDescription(),595itemNames7,596itemDesc7,597itemTypes7);598var itemValues7 = Stream.of(itemNames7)599.map(cd7::get)600.toArray();601var notmappableVal = new CompositeDataSupport(notmappable, itemNames7, itemValues7);602var attribute6 = new Attribute("DataPoint", notmappableVal);603var x4 = expectThrows(MBeanException.class,604standard ? () -> ((StandardMBean)mbean7).setAttribute(attribute6)605: () -> server.setAttribute(recname7, attribute6));606reportExpected(x4);607assertEquals(originalCause(x4).getClass(), InvalidObjectException.class);608609}610611static final void reportExpected(Throwable x) {612System.out.println("\nGot expected exception: " + x);613Throwable cause = x;614while ((cause = cause.getCause()) != null) {615System.out.println("\tCaused by: " + cause);616}617}618619static final Throwable originalCause(Throwable t) {620while (t.getCause() != null) t = t.getCause();621return t;622}623624static void println(String name, CompositeData cd) {625var cdt = cd.getCompositeType();626System.out.printf("%s: %s %s\n", name, cdt.getTypeName(),627cdt.keySet().stream()628.map(k -> k + "=" + cd.get(k))629.collect(Collectors.joining(", ", "{ ", " }")));630631}632633}634635636