diff --git a/src/main/java/org/weakref/jmx/MBeanExporter.java b/src/main/java/org/weakref/jmx/MBeanExporter.java index b213ee6..0176a7f 100644 --- a/src/main/java/org/weakref/jmx/MBeanExporter.java +++ b/src/main/java/org/weakref/jmx/MBeanExporter.java @@ -45,7 +45,7 @@ public class MBeanExporter private final MBeanServer server; private final Map exportedObjects; private final ObjectNameGenerator objectNameGenerator; - private final Map exportedManagedClasses = new ConcurrentHashMap<>(); + private final Map exportedManagedObjectExports = new ConcurrentHashMap<>(); public MBeanExporter(MBeanServer server) { @@ -74,8 +74,9 @@ public void destroy() public MBeanExport exportWithGeneratedName(Object object) { requireNonNull(object, "object is null"); - ObjectName objectName = createObjectName(objectNameGenerator.generatedNameOf(object.getClass())); - export(objectName, object); + Class type = object.getClass(); + ObjectName objectName = createObjectName(objectNameGenerator.generatedNameOf(type)); + export(objectName, object, Optional.of(type), Optional.empty(), ImmutableMap.of()); return new MBeanExport(objectName, () -> unexport(objectName)); } @@ -84,7 +85,16 @@ public MBeanExport exportWithGeneratedName(Object object, Class type) requireNonNull(object, "object is null"); requireNonNull(type, "type is null"); ObjectName objectName = createObjectName(objectNameGenerator.generatedNameOf(type)); - export(objectName, object); + export(objectName, object, Optional.of(type), Optional.empty(), ImmutableMap.of()); + return new MBeanExport(objectName, () -> unexport(objectName)); + } + + public MBeanExport exportWithGeneratedName(ObjectName objectName, Object object, Class type) + { + requireNonNull(objectName, "objectName is null"); + requireNonNull(object, "object is null"); + requireNonNull(type, "type is null"); + export(objectName, object, Optional.of(type), Optional.empty(), ImmutableMap.of()); return new MBeanExport(objectName, () -> unexport(objectName)); } @@ -94,7 +104,17 @@ public MBeanExport exportWithGeneratedName(Object object, Class type, String requireNonNull(type, "type is null"); requireNonNull(name, "name is null"); ObjectName objectName = createObjectName(objectNameGenerator.generatedNameOf(type, name)); - export(objectName, object); + export(objectName, object, Optional.of(type), Optional.of(name), ImmutableMap.of()); + return new MBeanExport(objectName, () -> unexport(objectName)); + } + + public MBeanExport exportWithGeneratedName(ObjectName objectName, Object object, Class type, String name) + { + requireNonNull(objectName, "objectName is null"); + requireNonNull(object, "object is null"); + requireNonNull(type, "type is null"); + requireNonNull(name, "name is null"); + export(objectName, object, Optional.of(type), Optional.of(name), ImmutableMap.of()); return new MBeanExport(objectName, () -> unexport(objectName)); } @@ -104,7 +124,7 @@ public MBeanExport exportWithGeneratedName(Object object, Class type, Map unexport(objectName)); } @@ -113,8 +133,34 @@ public void export(String name, Object object) export(createObjectName(name), object); } + public void export(String name, Object object, Class type) + { + export(createObjectName(name), object, type); + } + public void export(ObjectName objectName, Object object) { + export(objectName, object, Optional.empty(), Optional.empty(), ImmutableMap.of()); + } + + public void export(ObjectName objectName, Object object, Class type) + { + requireNonNull(type, "type is null"); + export(objectName, object, Optional.of(type), Optional.empty(), ImmutableMap.of()); + } + + private void export( + ObjectName objectName, + Object object, + Optional> exportedType, + Optional originalName, + Map originalProperties) + { + requireNonNull(objectName, "objectName is null"); + requireNonNull(object, "object is null"); + requireNonNull(exportedType, "exportedType is null"); + requireNonNull(originalName, "originalName is null"); + requireNonNull(originalProperties, "originalProperties is null"); try { MBeanBuilder builder = new MBeanBuilder(object); MBean mbean = builder.build(); @@ -127,7 +173,8 @@ public void export(ObjectName objectName, Object object) exportedObjects.put(objectName, object); } - exportedManagedClasses.put(objectName, ManagedClass.fromExportedObject(object)); + ManagedClass managedClass = ManagedClass.fromExportedObject(object); + exportedManagedObjectExports.put(objectName, new ManagedObjectExport(objectName, exportedType, originalName, originalProperties, managedClass)); } catch (InstanceAlreadyExistsException e) { throw new JmxException(Reason.INSTANCE_ALREADY_EXISTS, e.getMessage()); @@ -174,7 +221,7 @@ public void unexport(ObjectName objectName) exportedObjects.remove(objectName); } - exportedManagedClasses.remove(objectName); + exportedManagedObjectExports.remove(objectName); } catch (MBeanRegistrationException e) { throw new JmxException(Reason.MBEAN_REGISTRATION, e.getMessage(), e.getCause()); @@ -220,7 +267,7 @@ public Map unexportAllAndReportMissing() exportedObjects.keySet().removeAll(toRemove); - exportedManagedClasses.keySet().removeAll(toRemove); + exportedManagedObjectExports.keySet().removeAll(toRemove); } return errors; @@ -240,12 +287,20 @@ public Map getExportedObjects() public Map getManagedClasses() { ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Entry entry : exportedManagedClasses.entrySet()) { - builder.put(entry.getKey().toString(), entry.getValue()); + for (Entry entry : exportedManagedObjectExports.entrySet()) { + builder.put(entry.getKey().toString(), entry.getValue().getManagedClass()); } return builder.build(); } + /** + * Returns metadata for managed objects exported through this exporter, keyed by final {@link ObjectName}. + */ + public Map getManagedObjectExports() + { + return ImmutableMap.copyOf(exportedManagedObjectExports); + } + public Optional getExportedObject(ObjectName objectName) { synchronized (exportedObjects) { diff --git a/src/main/java/org/weakref/jmx/ManagedObjectExport.java b/src/main/java/org/weakref/jmx/ManagedObjectExport.java new file mode 100644 index 0000000..cceb2e6 --- /dev/null +++ b/src/main/java/org/weakref/jmx/ManagedObjectExport.java @@ -0,0 +1,85 @@ +/** + * Copyright 2009 Martin Traverso + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.weakref.jmx; + +import com.google.common.collect.ImmutableMap; + +import javax.management.ObjectName; + +import java.util.Map; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +/** + * Metadata for a managed object exported through an {@link MBeanExporter}. + */ +public final class ManagedObjectExport +{ + private final ObjectName objectName; + private final Optional> exportedType; + private final Optional originalName; + private final Map originalProperties; + private final ManagedClass managedClass; + + ManagedObjectExport( + ObjectName objectName, + Optional> exportedType, + Optional originalName, + Map originalProperties, + ManagedClass managedClass) + { + this.objectName = requireNonNull(objectName, "objectName is null"); + this.exportedType = requireNonNull(exportedType, "exportedType is null"); + this.originalName = requireNonNull(originalName, "originalName is null"); + this.originalProperties = ImmutableMap.copyOf(requireNonNull(originalProperties, "originalProperties is null")); + this.managedClass = requireNonNull(managedClass, "managedClass is null"); + } + + public ObjectName getObjectName() + { + return objectName; + } + + /** + * Original Java type supplied when the object was exported, if one was available. + */ + public Optional> getExportedType() + { + return exportedType; + } + + /** + * Original name argument passed to the {@link ObjectNameGenerator}, if any. + */ + public Optional getOriginalName() + { + return originalName; + } + + /** + * Original properties map passed to the {@link ObjectNameGenerator}, if any. + */ + public Map getOriginalProperties() + { + return originalProperties; + } + + public ManagedClass getManagedClass() + { + return managedClass; + } +} diff --git a/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java b/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java index 72ab9bb..3c6d819 100644 --- a/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java +++ b/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java @@ -20,13 +20,9 @@ import org.weakref.jmx.MBeanExporter; import org.weakref.jmx.ObjectNameGenerator; -import javax.management.ObjectName; - import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; -import java.util.function.BiFunction; class GuiceMBeanExporter { @@ -61,13 +57,10 @@ private static Set> castSetMapping(Object setMappings) private static void exportMaps(Set> mapMappings, MBeanExporter exporter, Injector injector, ObjectNameGenerator objectNameGenerator) { for (MapMapping mapping : mapMappings) { - BiFunction, ObjectName> namingFunction = mapping.getObjectNameFunction(); - Map map = injector.getInstance(mapping.getKey()); for (Map.Entry entry : map.entrySet()) { - ObjectName name = namingFunction.apply(objectNameGenerator, entry); - exporter.export(name, entry.getValue()); + mapping.export(exporter, objectNameGenerator, entry); } } } @@ -75,13 +68,10 @@ private static void exportMaps(Set> mapMappings, MBeanEx private static void exportSets(Set> setMappings, MBeanExporter exporter, Injector injector, ObjectNameGenerator objectNameGenerator) { for (SetMapping mapping : setMappings) { - BiFunction namingFunction = mapping.getObjectNameFunction(); - Set set = injector.getInstance(mapping.getKey()); for (T instance : set) { - ObjectName name = namingFunction.apply(objectNameGenerator, instance); - exporter.export(name, instance); + mapping.export(exporter, objectNameGenerator, instance); } } } @@ -89,7 +79,7 @@ private static void exportSets(Set> setMappings, MBeanExporter private static void export(Set mappings, MBeanExporter exporter, Injector injector, ObjectNameGenerator objectNameGenerator) { for (Mapping mapping : mappings) { - exporter.export(mapping.getName(objectNameGenerator), injector.getInstance(mapping.getKey())); + mapping.export(exporter, objectNameGenerator, injector.getInstance(mapping.getKey())); } } } diff --git a/src/main/java/org/weakref/jmx/guice/MapExportBinder.java b/src/main/java/org/weakref/jmx/guice/MapExportBinder.java index 291b87c..2599942 100644 --- a/src/main/java/org/weakref/jmx/guice/MapExportBinder.java +++ b/src/main/java/org/weakref/jmx/guice/MapExportBinder.java @@ -3,7 +3,6 @@ import com.google.inject.multibindings.Multibinder; import org.weakref.jmx.ObjectNameGenerator; -import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import java.util.Map.Entry; @@ -24,17 +23,7 @@ public class MapExportBinder public void withGeneratedName(NamingFunction valueNamingFunction) { - BiFunction, ObjectName> nameFactory = (factory, entry) -> { - try { - String itemName = valueNamingFunction.name(entry.getValue()); - return new ObjectName(factory.generatedNameOf(valueClass, itemName)); - } - catch (MalformedObjectNameException e) { - throw new RuntimeException(e); - } - }; - - as(nameFactory); + binder.addBinding().toInstance(MapMapping.generatedName(keyClass, valueClass, (key, value) -> valueNamingFunction.name(value))); } public void withGeneratedName(ObjectNameFunction valueNamingFunction) @@ -44,17 +33,7 @@ public void withGeneratedName(ObjectNameFunction valueNamingFunction) public void withGeneratedName(MapNamingFunction valueNamingFunction) { - BiFunction, ObjectName> nameFactory = (factory, entry) -> { - try { - String itemName = valueNamingFunction.name(entry.getKey(), entry.getValue()); - return new ObjectName(factory.generatedNameOf(valueClass, itemName)); - } - catch (MalformedObjectNameException e) { - throw new RuntimeException(e); - } - }; - - as(nameFactory); + binder.addBinding().toInstance(MapMapping.generatedName(keyClass, valueClass, valueNamingFunction)); } public void withGeneratedName(MapObjectNameFunction valueNamingFunction) diff --git a/src/main/java/org/weakref/jmx/guice/MapMapping.java b/src/main/java/org/weakref/jmx/guice/MapMapping.java index 3458851..f0aa561 100644 --- a/src/main/java/org/weakref/jmx/guice/MapMapping.java +++ b/src/main/java/org/weakref/jmx/guice/MapMapping.java @@ -1,6 +1,7 @@ package org.weakref.jmx.guice; import com.google.inject.Key; +import org.weakref.jmx.MBeanExporter; import org.weakref.jmx.ObjectNameGenerator; import javax.management.ObjectName; @@ -13,20 +14,29 @@ class MapMapping { - private final BiFunction, ObjectName> objectNameFunction; private final Class keyClass; private final Class valueClass; + private final ExportAction exportAction; MapMapping(Class keyClass, Class valueClass, BiFunction, ObjectName> objectNameFunction) + { + this(keyClass, valueClass, (exporter, objectNameGenerator, entry) -> exporter.export(objectNameFunction.apply(objectNameGenerator, entry), entry.getValue(), valueClass)); + } + + private MapMapping(Class keyClass, Class valueClass, ExportAction exportAction) { this.keyClass = keyClass; this.valueClass = valueClass; - this.objectNameFunction = objectNameFunction; + this.exportAction = exportAction; } - public BiFunction, ObjectName> getObjectNameFunction() + public static MapMapping generatedName(Class keyClass, Class valueClass, MapNamingFunction namingFunction) { - return objectNameFunction; + return new MapMapping<>(keyClass, valueClass, (exporter, objectNameGenerator, entry) -> { + String name = namingFunction.name(entry.getKey(), entry.getValue()); + ObjectName objectName = Mapping.createObjectName(objectNameGenerator.generatedNameOf(valueClass, name)); + exporter.exportWithGeneratedName(objectName, entry.getValue(), valueClass, name); + }); } @SuppressWarnings("unchecked") @@ -34,4 +44,14 @@ public Key> getKey() { return (Key>) Key.get(mapOf(keyClass, valueClass)); } + + public void export(MBeanExporter exporter, ObjectNameGenerator objectNameGenerator, Entry entry) + { + exportAction.export(exporter, objectNameGenerator, entry); + } + + private interface ExportAction + { + void export(MBeanExporter exporter, ObjectNameGenerator objectNameGenerator, Entry entry); + } } diff --git a/src/main/java/org/weakref/jmx/guice/Mapping.java b/src/main/java/org/weakref/jmx/guice/Mapping.java index 3778ebd..d80bcf7 100644 --- a/src/main/java/org/weakref/jmx/guice/Mapping.java +++ b/src/main/java/org/weakref/jmx/guice/Mapping.java @@ -16,28 +16,69 @@ package org.weakref.jmx.guice; import com.google.inject.Key; +import org.weakref.jmx.MBeanExporter; import org.weakref.jmx.ObjectNameGenerator; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import java.util.Optional; import java.util.function.Function; class Mapping { - private final Function nameFactory; private final Key key; + private final ExportAction exportAction; Mapping(Function nameFactory, Key key) { - this.nameFactory = nameFactory; + this(key, (exporter, objectNameGenerator, object) -> exporter.export(nameFactory.apply(objectNameGenerator), object, key.getTypeLiteral().getRawType())); + } + + private Mapping(Key key, ExportAction exportAction) + { this.key = key; + this.exportAction = exportAction; } - public String getName(ObjectNameGenerator objectNameGenerator) + public static Mapping generatedName(Key key, Optional generatedName) { - return nameFactory.apply(objectNameGenerator); + return new Mapping(key, (exporter, objectNameGenerator, object) -> { + Class type = key.getTypeLiteral().getRawType(); + if (generatedName.isPresent()) { + String name = generatedName.get(); + ObjectName objectName = createObjectName(objectNameGenerator.generatedNameOf(type, name)); + exporter.exportWithGeneratedName(objectName, object, type, name); + } + else { + ObjectName objectName = createObjectName(objectNameGenerator.generatedNameOf(type)); + exporter.exportWithGeneratedName(objectName, object, type); + } + }); } public Key getKey() { return key; } + + public void export(MBeanExporter exporter, ObjectNameGenerator objectNameGenerator, Object object) + { + exportAction.export(exporter, objectNameGenerator, object); + } + + private interface ExportAction + { + void export(MBeanExporter exporter, ObjectNameGenerator objectNameGenerator, Object object); + } + + static ObjectName createObjectName(String name) + { + try { + return new ObjectName(name); + } + catch (MalformedObjectNameException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/org/weakref/jmx/guice/NamedExportBinder.java b/src/main/java/org/weakref/jmx/guice/NamedExportBinder.java index be15e61..fe2583a 100644 --- a/src/main/java/org/weakref/jmx/guice/NamedExportBinder.java +++ b/src/main/java/org/weakref/jmx/guice/NamedExportBinder.java @@ -20,6 +20,7 @@ import com.google.inject.name.Named; import org.weakref.jmx.ObjectNameGenerator; +import java.util.Optional; import java.util.function.Function; public class NamedExportBinder @@ -40,17 +41,17 @@ public void withGeneratedName() { if (key.getAnnotation() != null) { if (key.getAnnotation() instanceof Named annotation) { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType(), annotation.value())); + asGeneratedName(annotation.value()); } else { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType(), key.getAnnotation().annotationType().getSimpleName())); + asGeneratedName(key.getAnnotation().annotationType().getSimpleName()); } } else if (key.getAnnotationType() != null) { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType(), key.getAnnotationType().getSimpleName())); + asGeneratedName(key.getAnnotationType().getSimpleName()); } else { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType())); + binder.addBinding().toInstance(Mapping.generatedName(key, Optional.empty())); } } @@ -63,4 +64,9 @@ public void as(Function nameFactory) { binder.addBinding().toInstance(new Mapping(nameFactory, key)); } + + private void asGeneratedName(String name) + { + binder.addBinding().toInstance(Mapping.generatedName(key, Optional.of(name))); + } } diff --git a/src/main/java/org/weakref/jmx/guice/SetExportBinder.java b/src/main/java/org/weakref/jmx/guice/SetExportBinder.java index 4fcfa90..e896c65 100644 --- a/src/main/java/org/weakref/jmx/guice/SetExportBinder.java +++ b/src/main/java/org/weakref/jmx/guice/SetExportBinder.java @@ -3,7 +3,6 @@ import com.google.inject.multibindings.Multibinder; import org.weakref.jmx.ObjectNameGenerator; -import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import java.util.function.BiFunction; @@ -21,17 +20,7 @@ public class SetExportBinder public void withGeneratedName(final NamingFunction itemNamingFunction) { - BiFunction nameFactory = (factory, object) -> { - try { - String itemName = itemNamingFunction.name(object); - return new ObjectName(factory.generatedNameOf(clazz, itemName)); - } - catch (MalformedObjectNameException e) { - throw new RuntimeException(e); - } - }; - - as(nameFactory); + binder.addBinding().toInstance(SetMapping.generatedName(clazz, itemNamingFunction)); } public void withGeneratedName(final ObjectNameFunction itemNamingFunction) diff --git a/src/main/java/org/weakref/jmx/guice/SetMapping.java b/src/main/java/org/weakref/jmx/guice/SetMapping.java index 2d981ef..6cc6d7f 100644 --- a/src/main/java/org/weakref/jmx/guice/SetMapping.java +++ b/src/main/java/org/weakref/jmx/guice/SetMapping.java @@ -1,6 +1,7 @@ package org.weakref.jmx.guice; import com.google.inject.Key; +import org.weakref.jmx.MBeanExporter; import org.weakref.jmx.ObjectNameGenerator; import javax.management.ObjectName; @@ -12,18 +13,27 @@ class SetMapping { - private final BiFunction objectNameFunction; private final Class clazz; + private final ExportAction exportAction; SetMapping(Class key, BiFunction objectNameFunction) + { + this(key, (exporter, objectNameGenerator, object) -> exporter.export(objectNameFunction.apply(objectNameGenerator, object), object, key)); + } + + private SetMapping(Class key, ExportAction exportAction) { this.clazz = key; - this.objectNameFunction = objectNameFunction; + this.exportAction = exportAction; } - public BiFunction getObjectNameFunction() + public static SetMapping generatedName(Class key, NamingFunction namingFunction) { - return objectNameFunction; + return new SetMapping<>(key, (exporter, objectNameGenerator, object) -> { + String name = namingFunction.name(object); + ObjectName objectName = Mapping.createObjectName(objectNameGenerator.generatedNameOf(key, name)); + exporter.exportWithGeneratedName(objectName, object, key, name); + }); } @SuppressWarnings("unchecked") @@ -31,4 +41,14 @@ public Key> getKey() { return (Key>) Key.get(setOf(clazz)); } + + public void export(MBeanExporter exporter, ObjectNameGenerator objectNameGenerator, T object) + { + exportAction.export(exporter, objectNameGenerator, object); + } + + private interface ExportAction + { + void export(MBeanExporter exporter, ObjectNameGenerator objectNameGenerator, T object); + } } diff --git a/src/main/java/org/weakref/jmx/guice/StringMapExportBinder.java b/src/main/java/org/weakref/jmx/guice/StringMapExportBinder.java index 563a769..e3ece7a 100644 --- a/src/main/java/org/weakref/jmx/guice/StringMapExportBinder.java +++ b/src/main/java/org/weakref/jmx/guice/StringMapExportBinder.java @@ -1,13 +1,6 @@ package org.weakref.jmx.guice; import com.google.inject.multibindings.Multibinder; -import org.weakref.jmx.ObjectNameGenerator; - -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; - -import java.util.Map.Entry; -import java.util.function.BiFunction; public class StringMapExportBinder extends MapExportBinder @@ -22,15 +15,6 @@ public class StringMapExportBinder public void withGeneratedName() { - BiFunction, ObjectName> nameFactory = (factory, entry) -> { - try { - return new ObjectName(factory.generatedNameOf(valueClass, entry.getKey())); - } - catch (MalformedObjectNameException e) { - throw new RuntimeException(e); - } - }; - - as(nameFactory); + binder.addBinding().toInstance(MapMapping.generatedName(keyClass, valueClass, (key, value) -> key)); } } diff --git a/src/test/java/org/weakref/jmx/TestExporter.java b/src/test/java/org/weakref/jmx/TestExporter.java index 62a07ec..3760255 100644 --- a/src/test/java/org/weakref/jmx/TestExporter.java +++ b/src/test/java/org/weakref/jmx/TestExporter.java @@ -31,6 +31,7 @@ import javax.management.ReflectionException; import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -131,6 +132,79 @@ void testManagedClasses() } } + @Test + void testManagedObjectExportsForManualExports() + { + Map managedObjectExports = exporter.getManagedObjectExports(); + for (NamedObject namedObject : objects) { + ManagedObjectExport managedObjectExport = managedObjectExports.get(namedObject.objectName); + + assertThat(managedObjectExport).isNotNull(); + assertThat(managedObjectExport.getObjectName()).isEqualTo(namedObject.objectName); + assertThat(managedObjectExport.getExportedType()).isEmpty(); + assertThat(managedObjectExport.getOriginalName()).isEmpty(); + assertThat(managedObjectExport.getOriginalProperties()).isEmpty(); + assertThat(managedObjectExport.getManagedClass().getTarget()).isEqualTo(namedObject.object); + } + } + + @Test + void testManagedObjectExportsForTypedManualExports() + { + MBeanExporter exporter = new MBeanExporter(server); + ObjectName objectName = getUniqueObjectName(); + SimpleObject object = new SimpleObject(); + + exporter.export(objectName, object, TestExporter.class); + + ManagedObjectExport managedObjectExport = exporter.getManagedObjectExports().get(objectName); + assertThat(managedObjectExport.getObjectName()).isEqualTo(objectName); + assertThat(managedObjectExport.getExportedType()).contains(TestExporter.class); + assertThat(managedObjectExport.getOriginalName()).isEmpty(); + assertThat(managedObjectExport.getOriginalProperties()).isEmpty(); + assertThat(managedObjectExport.getManagedClass().getTarget()).isEqualTo(object); + } + + @Test + void testManagedObjectExportsForGeneratedNames() + { + MBeanExporter exporter = new MBeanExporter(server); + + SimpleObject defaultObject = new SimpleObject(); + MBeanExport defaultExport = exporter.exportWithGeneratedName(defaultObject, SimpleObject.class); + ManagedObjectExport defaultManagedObjectExport = exporter.getManagedObjectExports().get(defaultExport.getObjectName()); + assertThat(defaultManagedObjectExport.getObjectName()).isEqualTo(defaultExport.getObjectName()); + assertThat(defaultManagedObjectExport.getExportedType()).contains(SimpleObject.class); + assertThat(defaultManagedObjectExport.getOriginalName()).isEmpty(); + assertThat(defaultManagedObjectExport.getOriginalProperties()).isEmpty(); + assertThat(defaultManagedObjectExport.getManagedClass().getTarget()).isEqualTo(defaultObject); + + SimpleObject namedObject = new SimpleObject(); + MBeanExport namedExport = exporter.exportWithGeneratedName(namedObject, TestExporter.class, "queued"); + ManagedObjectExport namedManagedObjectExport = exporter.getManagedObjectExports().get(namedExport.getObjectName()); + assertThat(namedManagedObjectExport.getObjectName()).isEqualTo(namedExport.getObjectName()); + assertThat(namedManagedObjectExport.getExportedType()).contains(TestExporter.class); + assertThat(namedManagedObjectExport.getOriginalName()).contains("queued"); + assertThat(namedManagedObjectExport.getOriginalProperties()).isEmpty(); + assertThat(namedManagedObjectExport.getManagedClass().getTarget()).isEqualTo(namedObject); + + Map properties = new HashMap<>(); + properties.put("segment", "x"); + SimpleObject propertyObject = new SimpleObject(); + MBeanExport propertyExport = exporter.exportWithGeneratedName(propertyObject, TestExporter.class, properties); + properties.put("segment", "changed"); + + ManagedObjectExport propertyManagedObjectExport = exporter.getManagedObjectExports().get(propertyExport.getObjectName()); + assertThat(propertyManagedObjectExport.getObjectName()).isEqualTo(propertyExport.getObjectName()); + assertThat(propertyManagedObjectExport.getExportedType()).contains(TestExporter.class); + assertThat(propertyManagedObjectExport.getOriginalName()).isEmpty(); + assertThat(propertyManagedObjectExport.getOriginalProperties()).containsExactly(Map.entry("segment", "x")); + assertThat(propertyManagedObjectExport.getManagedClass().getTarget()).isEqualTo(propertyObject); + + namedExport.unexport(); + assertThat(exporter.getManagedObjectExports()).doesNotContainKey(namedExport.getObjectName()); + } + @Test void testDuplicateKey() { diff --git a/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java b/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java index fc64191..acdfcb7 100644 --- a/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java +++ b/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java @@ -21,13 +21,15 @@ import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.multibindings.Multibinder; +import com.google.inject.util.Modules; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; +import org.weakref.jmx.ManagedObjectExport; import org.weakref.jmx.MBeanExporter; import org.weakref.jmx.ObjectNameBuilder; import org.weakref.jmx.ObjectNameGenerator; import org.weakref.jmx.SimpleObject; import org.weakref.jmx.Util; +import org.weakref.jmx.testing.TestingMBeanServer; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; @@ -38,9 +40,12 @@ import java.lang.management.ManagementFactory; import java.util.Map; +import java.util.Optional; import static com.google.inject.Stage.PRODUCTION; import static com.google.inject.name.Names.named; +import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; +import static org.assertj.core.api.Assertions.assertThat; import static org.weakref.jmx.ObjectNames.generatedNameOf; public class TestMBeanModule @@ -92,6 +97,11 @@ protected void configure() MBeanServer server = injector.getInstance(MBeanServer.class); assertThat(server.getMBeanInfo(name)).isNotNull(); + ManagedObjectExport managedObjectExport = injector.getInstance(MBeanExporter.class).getManagedObjectExports().get(name); + assertThat(managedObjectExport.getObjectName()).isEqualTo(name); + assertThat(managedObjectExport.getExportedType()).contains(SimpleObject.class); + assertThat(managedObjectExport.getOriginalName()).isEmpty(); + assertThat(managedObjectExport.getOriginalProperties()).isEmpty(); server.unregisterMBean(name); } @@ -131,6 +141,42 @@ protected void configure() MBeanServer server = injector.getInstance(MBeanServer.class); assertThat(server.getMBeanInfo(name)).isNotNull(); + ManagedObjectExport managedObjectExport = injector.getInstance(MBeanExporter.class).getManagedObjectExports().get(name); + assertThat(managedObjectExport.getObjectName()).isEqualTo(name); + assertThat(managedObjectExport.getExportedType()).contains(SimpleObject.class); + assertThat(managedObjectExport.getOriginalName()).isEmpty(); + assertThat(managedObjectExport.getOriginalProperties()).isEmpty(); + server.unregisterMBean(name); + } + + @Test + public void testGeneratedNameOnAnnotationClassMetadata() + throws Exception + { + ObjectName name = new ObjectName(generatedNameOf(SimpleObject.class, TestAnnotation.class)); + + Injector injector = Guice.createInjector(PRODUCTION, new MBeanModule(), new AbstractModule() + { + @Override + protected void configure() + { + binder().requireExplicitBindings(); + binder().disableCircularProxies(); + + bind(SimpleObject.class).annotatedWith(TestAnnotation.class).toInstance(new SimpleObject()); + bind(MBeanServer.class).toInstance(ManagementFactory.getPlatformMBeanServer()); + ExportBinder.newExporter(binder()).export(SimpleObject.class).annotatedWith(TestAnnotation.class).withGeneratedName(); + } + }); + + MBeanServer server = injector.getInstance(MBeanServer.class); + + assertThat(server.getMBeanInfo(name)).isNotNull(); + ManagedObjectExport managedObjectExport = injector.getInstance(MBeanExporter.class).getManagedObjectExports().get(name); + assertThat(managedObjectExport.getObjectName()).isEqualTo(name); + assertThat(managedObjectExport.getExportedType()).contains(SimpleObject.class); + assertThat(managedObjectExport.getOriginalName()).contains(TestAnnotation.class.getSimpleName()); + assertThat(managedObjectExport.getOriginalProperties()).isEmpty(); server.unregisterMBean(name); } @@ -170,9 +216,53 @@ protected void configure() MBeanServer server = injector.getInstance(MBeanServer.class); assertThat(server.getMBeanInfo(name)).isNotNull(); + ManagedObjectExport managedObjectExport = injector.getInstance(MBeanExporter.class).getManagedObjectExports().get(name); + assertThat(managedObjectExport.getObjectName()).isEqualTo(name); + assertThat(managedObjectExport.getExportedType()).contains(SimpleObject.class); + assertThat(managedObjectExport.getOriginalName()).contains("hello"); + assertThat(managedObjectExport.getOriginalProperties()).isEmpty(); server.unregisterMBean(name); } + @Test + public void testGeneratedNameUsesConfiguredGenerator() + throws Exception + { + MBeanServer server = new TestingMBeanServer(); + ObjectNameGenerator configuredGenerator = new DomainObjectNameGenerator("from.binding"); + ObjectNameGenerator exporterGenerator = new DomainObjectNameGenerator("from.exporter"); + + Injector injector = Guice.createInjector(PRODUCTION, + Modules.override(new MBeanModule()).with(new AbstractModule() + { + @Override + protected void configure() + { + binder().requireExplicitBindings(); + binder().disableCircularProxies(); + + bind(MBeanServer.class).toInstance(server); + newOptionalBinder(binder(), ObjectNameGenerator.class).setBinding().toInstance(configuredGenerator); + bind(MBeanExporter.class).toInstance(new MBeanExporter(server, Optional.of(exporterGenerator))); + bind(SimpleObject.class).annotatedWith(named("generated")).toInstance(new SimpleObject()); + bind(SimpleObject.class).annotatedWith(named("custom")).toInstance(new SimpleObject()); + + ExportBinder exporter = ExportBinder.newExporter(binder()); + exporter.export(SimpleObject.class).annotatedWith(named("generated")).withGeneratedName(); + exporter.export(SimpleObject.class).annotatedWith(named("custom")).as(generator -> generator.generatedNameOf(SimpleObject.class, "custom")); + } + })); + + ObjectName generatedName = new ObjectName("from.binding:type=SimpleObject,name=generated"); + ObjectName customName = new ObjectName("from.binding:type=SimpleObject,name=custom"); + ObjectName generatedNameFromExporter = new ObjectName("from.exporter:type=SimpleObject,name=generated"); + + Map exports = injector.getInstance(MBeanExporter.class).getManagedObjectExports(); + assertThat(exports).containsKeys(generatedName, customName); + assertThat(exports).doesNotContainKey(generatedNameFromExporter); + assertThat(exports.get(generatedName).getOriginalName()).contains("generated"); + } + @Test public void testAnnotation() throws Exception @@ -351,4 +441,16 @@ public String generatedNameOf(Class type, Map properties) .build(); } } + + private record DomainObjectNameGenerator(String domain) + implements ObjectNameGenerator + { + @Override + public String generatedNameOf(Class type, Map properties) + { + return new ObjectNameBuilder(domain) + .withProperties(properties) + .build(); + } + } }