diff --git a/pom.xml b/pom.xml index ba176ac..7adbf54 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,13 @@ 3.0.0 true + + + org.assertj + assertj-core + 3.27.6 + test + diff --git a/src/main/java/org/weakref/jmx/MBeanExporter.java b/src/main/java/org/weakref/jmx/MBeanExporter.java index 7ef0aad..cddbeb4 100644 --- a/src/main/java/org/weakref/jmx/MBeanExporter.java +++ b/src/main/java/org/weakref/jmx/MBeanExporter.java @@ -18,9 +18,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.MapMaker; import com.google.inject.Inject; +import jakarta.annotation.PreDestroy; import org.weakref.jmx.JmxException.Reason; -import jakarta.annotation.PreDestroy; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.MBeanRegistrationException; @@ -47,21 +47,22 @@ public class MBeanExporter private final ObjectNameGenerator objectNameGenerator; private final Map exportedManagedClasses = new ConcurrentHashMap<>(); - MBeanExporter() - { - this(ManagementFactory.getPlatformMBeanServer()); - } - + /** + * @deprecated Use {@link #MBeanExporter(MBeanServer, ObjectNameGenerator)} instead. + * You can obtain an instance of {@link ObjectNameGenerator} via {@link ObjectNameGenerator#defaultObjectNameGenerator()} + * if usage of the mbean exporter requires no namespacing. + */ + @Deprecated public MBeanExporter(MBeanServer server) { - this(server, Optional.empty()); + this(server, ObjectNameGenerator.defaultObjectNameGenerator()); } @Inject - public MBeanExporter(MBeanServer server, Optional objectNameGenerator) + public MBeanExporter(MBeanServer server, ObjectNameGenerator objectNameGenerator) { - this.server = server; - this.objectNameGenerator = objectNameGenerator.orElseGet(ObjectNameGenerator::defaultObjectNameGenerator); + this.server = requireNonNull(server, "server is null"); + this.objectNameGenerator = requireNonNull(objectNameGenerator, "objectNameGenerator is null"); exportedObjects = new MapMaker().weakValues().makeMap(); } @@ -265,7 +266,9 @@ public Optional getExportedObject(ObjectName objectName) */ public static MBeanExporter withPlatformMBeanServer() { - return new MBeanExporter(ManagementFactory.getPlatformMBeanServer()); + return new MBeanExporter( + ManagementFactory.getPlatformMBeanServer(), + ObjectNameGenerator.defaultObjectNameGenerator()); } private static ObjectName createObjectName(String name) diff --git a/src/main/java/org/weakref/jmx/guice/AnnotatedExportBuilder.java b/src/main/java/org/weakref/jmx/guice/AnnotatedExportBuilder.java deleted file mode 100644 index cd3a507..0000000 --- a/src/main/java/org/weakref/jmx/guice/AnnotatedExportBuilder.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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.guice; - -import com.google.inject.Key; -import com.google.inject.multibindings.Multibinder; - -import java.lang.annotation.Annotation; - -@Deprecated -public class AnnotatedExportBuilder - extends NamedBindingBuilder -{ - AnnotatedExportBuilder(Multibinder binder, Key key) - { - super(binder, key); - } - - public NamedBindingBuilder annotatedWith(Annotation annotation) - { - return new NamedBindingBuilder(binder, Key.get(key.getTypeLiteral(), annotation)); - } - - public NamedBindingBuilder annotatedWith(Class annotationClass) - { - return new NamedBindingBuilder(binder, Key.get(key.getTypeLiteral(), annotationClass)); - } -} diff --git a/src/main/java/org/weakref/jmx/guice/ExportBuilder.java b/src/main/java/org/weakref/jmx/guice/ExportBuilder.java deleted file mode 100644 index 0efbed7..0000000 --- a/src/main/java/org/weakref/jmx/guice/ExportBuilder.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.weakref.jmx.guice; - -import com.google.inject.Key; -import com.google.inject.multibindings.Multibinder; - -@Deprecated -public class ExportBuilder -{ - private final Multibinder binder; - - ExportBuilder(Multibinder binder) - { - this.binder = binder; - } - - public AnnotatedExportBuilder export(Class clazz) - { - return new AnnotatedExportBuilder(binder, Key.get(clazz)); - } - - public NamedBindingBuilder export(Key key) - { - return new NamedBindingBuilder(binder, key); - } -} diff --git a/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java b/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java index 72ab9bb..8b54d49 100644 --- a/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java +++ b/src/main/java/org/weakref/jmx/guice/GuiceMBeanExporter.java @@ -24,7 +24,6 @@ import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; @@ -35,15 +34,14 @@ public GuiceMBeanExporter(Set mappings, Set> setMappings, Set> mapMappings, MBeanExporter exporter, - Optional objectNameGenerator, + ObjectNameGenerator objectNameGenerator, Injector injector) { - ObjectNameGenerator generator = objectNameGenerator.orElseGet(ObjectNameGenerator::defaultObjectNameGenerator); - export(mappings, exporter, injector, generator); + export(mappings, exporter, injector, objectNameGenerator); // cast to Object to get around Java's broken generics - exportSets(castSetMapping(setMappings), exporter, injector, generator); - exportMaps(castMapMappings(mapMappings), exporter, injector, generator); + exportSets(castSetMapping(setMappings), exporter, injector, objectNameGenerator); + exportMaps(castMapMappings(mapMappings), exporter, injector, objectNameGenerator); } @SuppressWarnings("unchecked") diff --git a/src/main/java/org/weakref/jmx/guice/MBeanModule.java b/src/main/java/org/weakref/jmx/guice/MBeanModule.java index ddb0c48..4bc0c9a 100644 --- a/src/main/java/org/weakref/jmx/guice/MBeanModule.java +++ b/src/main/java/org/weakref/jmx/guice/MBeanModule.java @@ -16,82 +16,166 @@ package org.weakref.jmx.guice; import com.google.inject.AbstractModule; -import com.google.inject.Binder; -import com.google.inject.Key; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import org.weakref.jmx.MBeanExporter; import org.weakref.jmx.ObjectNameGenerator; +import javax.management.MBeanServer; + +import java.util.Optional; +import java.util.Set; + import static com.google.inject.multibindings.Multibinder.newSetBinder; import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; +import static java.util.Objects.requireNonNull; -public class MBeanModule +public final class MBeanModule extends AbstractModule { - private ExportBuilder builder; - @Override - protected final void configure() + public static MBeanModule withUnnamespacedObjectNames() { - builder = newExporter(binder()); - - bind(GuiceMBeanExporter.class).asEagerSingleton(); - bind(MBeanExporter.class).in(Scopes.SINGLETON); - - newOptionalBinder(binder(), ObjectNameGenerator.class); - newSetBinder(binder(), new TypeLiteral>() {}); - newSetBinder(binder(), new TypeLiteral>() {}); + return new MBeanModule(ObjectNameGeneratorStrategy.NO_NAMESPACE); + } - configureMBeans(); + public static MBeanModule forCustomObjectNameGenerator() + { + return new MBeanModule(ObjectNameGeneratorStrategy.EXPLICIT_NAMING); } + private final ObjectNameGeneratorStrategy objectNameGeneratorStrategy; + /** - * To be overridden by subclasses. E.g., - * - * protected void configureMBeans() { - * export(ManagedObject.class).as("test:name=X"); - * export(ManagedObject.class).annotatedWith(SomeAnnotation.class).as("test:name=Y"); - * } - * - * When ExportBuilder is used, a raw MBeanModule can be imported to trigger the - * registration of exported mbeans: - * - *
-     * {@code
-     * Injector injector = Guice.createInjector(new MBeanModule(),
-     *      new AbstractModule() {
-     *          @Override
-     *          protected void configure() {
-     *              ExportBuilder builder = MBeanModule.newExporter();
-     *              builder.export(AnotherManagedObject.class).as("test:name="Z");
-     *          }
-     *      });
-     * }
-     * 
- * - * @deprecated subclassing no longer supported. Use ExportBinder instead + * @deprecated It should be explicit choice to export MBeans without namespacing, as this can easily lead to name collisions. + * Use {@link #withUnnamespacedObjectNames()} if you want this behavior and use {@link #forCustomObjectNameGenerator()} if you + * want to provide your own naming strategy perhaps for namespacing. */ @Deprecated - protected void configureMBeans() { + public MBeanModule() + { + this(ObjectNameGeneratorStrategy.LEGACY); } - @Deprecated - protected NamedBindingBuilder export(Key key) + private MBeanModule(ObjectNameGeneratorStrategy objectNameGeneratorStrategy) { - return builder.export(key); + this.objectNameGeneratorStrategy = requireNonNull(objectNameGeneratorStrategy, "objectNameGeneratorStrategy is null"); } - @Deprecated - protected AnnotatedExportBuilder export(Class clazz) + @Override + protected void configure() + { + switch (objectNameGeneratorStrategy) { + case LEGACY -> { + bind(GuiceMBeanExporter.class) + .toProvider(LegacyBindingGuiceMBeanExporterProvider.class) + .asEagerSingleton(); + bind(MBeanExporter.class) + .toProvider(LegacyBindingMBeanExporterProvider.class) + .in(Scopes.SINGLETON); + newOptionalBinder(binder(), ObjectNameGenerator.class); + } + + case NO_NAMESPACE -> { + bind(GuiceMBeanExporter.class).asEagerSingleton(); + bind(MBeanExporter.class).in(Scopes.SINGLETON); + bind(ObjectNameGenerator.class).toInstance(ObjectNameGenerator.defaultObjectNameGenerator()); + } + + case EXPLICIT_NAMING -> { + bind(GuiceMBeanExporter.class).asEagerSingleton(); + bind(MBeanExporter.class).in(Scopes.SINGLETON); + // require explicit binding + bind(ObjectNameGenerator.class); + } + } + + newSetBinder(binder(), new TypeLiteral() {}); + newSetBinder(binder(), new TypeLiteral>() {}); + newSetBinder(binder(), new TypeLiteral>() {}); + } + + enum ObjectNameGeneratorStrategy { - return builder.export(clazz); + /** + * As it used to be - user can export mbeans with the {@link ObjectNameGenerator#defaultObjectNameGenerator() default strategy}, + * or bind their own generator to {@code ObjectNameGenerator.class} key (or via optional binder) + * + * @deprecated exists only to support deprecated legacy mode + */ + @Deprecated + LEGACY, + + /** + * This strategy forces user can export mbeans with the {@link ObjectNameGenerator#defaultObjectNameGenerator() default strategy}. + * If custom strategy is needed, {@link #EXPLICIT_NAMING} should be used. + * This strategy is suitable when MBeanModule is used in top level Guice context of an application. + */ + NO_NAMESPACE, + + /** + * This strategy requires that {@link ObjectNameGenerator} is separately bound. + * This strategy is suitable when MBeanModule is used in a library/module Guice context, + * where lack of namespacing may lead to name collisions. + */ + EXPLICIT_NAMING, } - @Deprecated - public static ExportBuilder newExporter(Binder binder) + private static class LegacyBindingGuiceMBeanExporterProvider + implements Provider { - return new ExportBuilder(newSetBinder(binder, Mapping.class)); + private final Set mappings; + private final Set> setMappings; + private final Set> mapMappings; + private final MBeanExporter exporter; + private final ObjectNameGenerator objectNameGenerator; + private final Injector injector; + + @Inject + public LegacyBindingGuiceMBeanExporterProvider( + Set mappings, + Set> setMappings, + Set> mapMappings, + MBeanExporter exporter, + Optional objectNameGenerator, + Injector injector) + { + this.mappings = requireNonNull(mappings, "mappings is null"); + this.setMappings = requireNonNull(setMappings, "setMappings is null"); + this.mapMappings = requireNonNull(mapMappings, "mapMappings is null"); + this.exporter = requireNonNull(exporter, "exporter is null"); + this.objectNameGenerator = objectNameGenerator.orElseGet(ObjectNameGenerator::defaultObjectNameGenerator); + this.injector = requireNonNull(injector, "injector is null"); + } + + @Override + public GuiceMBeanExporter get() + { + return new GuiceMBeanExporter(mappings, setMappings, mapMappings, exporter, objectNameGenerator, injector); + } } + private static class LegacyBindingMBeanExporterProvider + implements Provider + { + + private final MBeanServer server; + private final ObjectNameGenerator objectNameGenerator; + + @Inject + public LegacyBindingMBeanExporterProvider(MBeanServer server, Optional objectNameGenerator) + { + this.server = requireNonNull(server, "server is null"); + this.objectNameGenerator = objectNameGenerator.orElseGet(ObjectNameGenerator::defaultObjectNameGenerator); + } + + @Override + public MBeanExporter get() + { + return new MBeanExporter(server, objectNameGenerator); + } + } } diff --git a/src/main/java/org/weakref/jmx/guice/NamedBindingBuilder.java b/src/main/java/org/weakref/jmx/guice/NamedBindingBuilder.java deleted file mode 100644 index 1eed531..0000000 --- a/src/main/java/org/weakref/jmx/guice/NamedBindingBuilder.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * 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.guice; - -import com.google.inject.Key; -import com.google.inject.multibindings.Multibinder; -import com.google.inject.name.Named; -import org.weakref.jmx.ObjectNameGenerator; - -import java.util.function.Function; - -@Deprecated -public class NamedBindingBuilder -{ - protected final Multibinder binder; - protected final Key key; - - NamedBindingBuilder(Multibinder binder, Key key) - { - this.binder = binder; - this.key = key; - } - - /** - * Names the MBean according to {@link org.weakref.jmx.ObjectNames} name generator methods. - */ - public void withGeneratedName() - { - if (key.getAnnotation() != null) { - if (key.getAnnotation() instanceof Named annotation) { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType(), annotation.value())); - } - else { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType(), key.getAnnotation().annotationType().getSimpleName())); - } - } - else if (key.getAnnotationType() != null) { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType(), key.getAnnotationType().getSimpleName())); - } - else { - as(factory -> factory.generatedNameOf(key.getTypeLiteral().getRawType())); - } - } - - public void as(String name) - { - as(factory -> name); - } - - private void as(Function mappingNameFactory) - { - binder.addBinding().toInstance(new Mapping(mappingNameFactory, key)); - } -} diff --git a/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java b/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java index 2d6b9cf..6bdbd17 100644 --- a/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java +++ b/src/test/java/org/weakref/jmx/guice/TestMBeanModule.java @@ -23,6 +23,7 @@ import com.google.inject.multibindings.Multibinder; import org.testng.Assert; import org.testng.annotations.Test; +import org.weakref.jmx.MBeanExporter; import org.weakref.jmx.ObjectNameBuilder; import org.weakref.jmx.ObjectNameGenerator; import org.weakref.jmx.SimpleObject; @@ -40,10 +41,38 @@ import static com.google.inject.Stage.PRODUCTION; import static com.google.inject.name.Names.named; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.weakref.jmx.ObjectNames.generatedNameOf; public class TestMBeanModule { + @Test + public void testLegacy() + { + Injector injector = Guice.createInjector( + new MBeanModule(), + binder -> binder.bind(MBeanServer.class).toInstance(ManagementFactory.getPlatformMBeanServer())); + injector.getInstance(MBeanExporter.class); + } + + @Test + public void testWithUnnamespacedObjectNames() + { + Injector injector = Guice.createInjector( + MBeanModule.withUnnamespacedObjectNames(), + binder -> binder.bind(MBeanServer.class).toInstance(ManagementFactory.getPlatformMBeanServer())); + injector.getInstance(MBeanExporter.class); + } + + @Test + public void testForCustomObjectNameGenerator() + { + assertThatThrownBy(() -> Guice.createInjector( + MBeanModule.forCustomObjectNameGenerator(), + binder -> binder.bind(MBeanServer.class).toInstance(ManagementFactory.getPlatformMBeanServer()))) + .hasMessageContaining("No implementation for ObjectNameGenerator was bound"); + } + @Test public void testExportedInDevelopmentStageToo() throws Exception @@ -268,6 +297,19 @@ protected void configure() server.unregisterMBean(objectName2); } + @Test + public void testNothingExported() + { + Injector injector = Guice.createInjector( + new MBeanModule(), + binder -> { + binder.requireExplicitBindings(); + binder.disableCircularProxies(); + binder.bind(MBeanServer.class).toInstance(ManagementFactory.getPlatformMBeanServer()); + }); + injector.getInstance(MBeanExporter.class); + } + @Test public void testSet() throws Exception diff --git a/src/test/java/org/weakref/jmx/guice/TestModuleSubclassing.java b/src/test/java/org/weakref/jmx/guice/TestModuleSubclassing.java deleted file mode 100644 index ec2e168..0000000 --- a/src/test/java/org/weakref/jmx/guice/TestModuleSubclassing.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * 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.guice; - -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Key; -import com.google.inject.Module; -import com.google.inject.Stage; -import com.google.inject.name.Named; -import com.google.inject.name.Names; -import org.testng.Assert; -import org.testng.annotations.Test; -import org.weakref.jmx.Managed; -import org.weakref.jmx.testing.TestingMBeanServer; - -import javax.management.MBeanServer; -import javax.management.ObjectName; - -public class TestModuleSubclassing -{ - @Test - public void testSubclassing() - throws Exception - { - final Named name1 = Names.named("bean1"); - final Named name2 = Names.named("bean2"); - - final TestingMBeanServer tms = new TestingMBeanServer(); - - final Module m1 = new MBeanModule() { - @Override - protected void configureMBeans() { - export(Key.get(Bean.class, name1)).as("bean1:name=bean1"); - } - }; - - final Module m2 = new MBeanModule() { - @Override - protected void configureMBeans() { - export(Key.get(Bean.class, name2)).as("bean2:name=bean2"); - } - }; - - Guice.createInjector(Stage.PRODUCTION, - m1, - m2, - new AbstractModule() { - @Override - public void configure() { - bind(MBeanServer.class).toInstance(tms); - bind(Bean.class).annotatedWith(name1).toInstance(new Bean("name1")); - bind(Bean.class).annotatedWith(name2).toInstance(new Bean("name2")); - } - }); - - Assert.assertEquals(2, tms.getMBeanCount().intValue()); - Assert.assertNotNull(tms.getMBeanInfo(new ObjectName("bean1:name=bean1"))); - Assert.assertNotNull(tms.getMBeanInfo(new ObjectName("bean2:name=bean2"))); - } - - public static final class Bean - { - private final String name; - - Bean(final String name) - { - this.name = name; - } - - @Managed - public String getName() - { - return name; - } - } -} -