Implemented #599, ability to skip duplicate module registrations
diff --git a/release-notes/VERSION b/release-notes/VERSION
index e04225d..bf992f6 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -38,6 +38,7 @@
#597: Improve error messaging for cases where JSON Creator returns null (which
is illegal)
(contributed by Aurelien L)
+#599: Add a simple mechanism for avoiding multiple registrations of the same module
#607: Allow (re)config of `JsonParser.Feature`s via `ObjectReader`
#608: Allow (re)config of `JsonGenerator.Feature`s via `ObjectWriter`
#614: Add a mechanism for using `@JsonCreator.mode` for resolving possible ambiguity between
diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
index e7fbf21..83c304a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
@@ -274,7 +274,33 @@
*
* @since 2.1
*/
- USE_WRAPPER_NAME_AS_PROPERTY_NAME(false)
+ USE_WRAPPER_NAME_AS_PROPERTY_NAME(false),
+
+ /*
+ /******************************************************
+ /* Other features
+ /******************************************************
+ */
+
+ /**
+ * Feature that determines whether multiple registrations of same module
+ * should be ignored or not; if enabled, only the first registration call
+ * results in module being called, and possible duplicate calls are silently
+ * ignored; if disabled, no checking is done and all registration calls are
+ * dispatched to module.
+ *<p>
+ * Definition of "same module" is based on using {@link Module#getTypeId()};
+ * modules with same non-null <code>type id</code> are considered same for
+ * purposes of duplicate registration. This also avoids having to keep track
+ * of actual module instances; only ids will be kept track of (and only if
+ * this feature is enabled).
+ *<p>
+ * Feature is enabled by default.
+ *
+ * @since 2.5
+ */
+ IGNORE_DUPLICATE_MODULE_REGISTRATIONS(true)
+
;
private final boolean _defaultState;
diff --git a/src/main/java/com/fasterxml/jackson/databind/Module.java b/src/main/java/com/fasterxml/jackson/databind/Module.java
index 1b268a5..37ca295 100644
--- a/src/main/java/com/fasterxml/jackson/databind/Module.java
+++ b/src/main/java/com/fasterxml/jackson/databind/Module.java
@@ -28,7 +28,7 @@
*/
/**
- * Method that returns identifier for module; this can be used by Jackson
+ * Method that returns a display that can be used by Jackson
* for informational purposes, as well as in associating extensions with
* module that provides them.
*/
@@ -41,6 +41,23 @@
@Override
public abstract Version version();
+ /**
+ * Method that returns an id that may be used to determine if two {@link Module}
+ * instances are considered to be of same type, for purpose of preventing
+ * multiple registrations of "same type of" module
+ * (see {@link com.fasterxml.jackson.databind.MapperFeature#IGNORE_DUPLICATE_MODULE_REGISTRATIONS})
+ * If `null` is returned, every instance is considered unique.
+ * If non-null value is returned, equality of id Objects is used to check whether
+ * modules should be considered to be "of same type"
+ *<p>
+ * Default implementation returns value of class name ({@link Class#getName}).
+ *
+ * @since 2.5
+ */
+ public Object getTypeId() {
+ return getClass().getName();
+ }
+
/*
/**********************************************************
/* Life-cycle: registration
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
index 9271bad..93f6534 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
@@ -324,6 +324,22 @@
/*
/**********************************************************
+ /* Module-related
+ /**********************************************************
+ */
+
+ /**
+ * Set of module types (as per {@link Module#getTypeId()} that have been
+ * registered; kept track of iff {@link MapperFeature#IGNORE_DUPLICATE_MODULE_REGISTRATIONS}
+ * is enabled, so that duplicate registration calls can be ignored
+ * (to avoid adding same handlers multiple times, mostly).
+ *
+ * @since 2.5
+ */
+ protected Set<Object> _registeredModuleTypes;
+
+ /*
+ /**********************************************************
/* Caching
/**********************************************************
*/
@@ -601,6 +617,19 @@
*/
public ObjectMapper registerModule(Module module)
{
+ if (isEnabled(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS)) {
+ Object typeId = module.getTypeId();
+ if (typeId != null) {
+ if (_registeredModuleTypes == null) {
+ _registeredModuleTypes = new HashSet<Object>();
+ }
+ // try adding; if already had it, should skip
+ if (!_registeredModuleTypes.add(typeId)) {
+ return this;
+ }
+ }
+ }
+
/* Let's ensure we have access to name and version information,
* even if we do not have immediate use for either. This way we know
* that they will be available from beginning
@@ -756,7 +785,7 @@
@Override
public void setMixInAnnotations(Class<?> target, Class<?> mixinSource) {
- mapper.addMixInAnnotations(target, mixinSource);
+ mapper.addMixIn(target, mixinSource);
}
@Override
@@ -3067,7 +3096,6 @@
* Method called to configure the generator as necessary and then
* call write functionality
*/
- @SuppressWarnings("deprecation")
protected final void _configAndWriteValue(JsonGenerator jgen, Object value)
throws IOException
{
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
index e451098..de0c6fe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
@@ -12,7 +12,7 @@
public class DeserializerFactoryConfig
implements java.io.Serializable // since 2.1
{
- private static final long serialVersionUID = 3683541151102256824L;
+ private static final long serialVersionUID = 1L; // since 2.5
protected final static Deserializers[] NO_DESERIALIZERS = new Deserializers[0];
protected final static BeanDeserializerModifier[] NO_MODIFIERS = new BeanDeserializerModifier[0];
@@ -47,7 +47,6 @@
*/
protected final BeanDeserializerModifier[] _modifiers;
-
/**
* List of objects that may be able to resolve abstract types to
* concrete types. Used by functionality like "mr Bean" to materialize
@@ -63,7 +62,7 @@
* or to support post-constructor functionality.
*/
protected final ValueInstantiators[] _valueInstantiators;
-
+
/**
* Constructor for creating basic configuration with no additional
* handlers.
@@ -106,6 +105,7 @@
return new DeserializerFactoryConfig(all, _additionalKeyDeserializers, _modifiers,
_abstractTypeResolvers, _valueInstantiators);
}
+
/**
* Fluent/factory method used to construct a configuration object that
* has same key deserializer providers as this instance, plus one specified
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
index 2c20c1e..01fa522 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
@@ -807,7 +807,7 @@
if (typeStr != null) {
throw new IllegalArgumentException("Can not deserialize Class "+type.getName()+" (of type "+typeStr+") as a Bean");
}
- return true;
+ return true;
}
/**
@@ -821,7 +821,7 @@
if (status == null) {
BeanDescription desc = config.introspectClassAnnotations(type);
status = config.getAnnotationIntrospector().isIgnorableType(desc.getClassInfo());
- // We default to 'false', ie. not ignorable
+ // We default to 'false', i.e. not ignorable
if (status == null) {
status = Boolean.FALSE;
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
index 6a33afb..f6485b8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
+++ b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
@@ -6,7 +6,6 @@
import java.util.Map;
import com.fasterxml.jackson.core.Version;
-
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
@@ -30,8 +29,7 @@
extends Module
implements java.io.Serializable
{
- // at 2.4.0:
- private static final long serialVersionUID = -8905749147637667249L;
+ private static final long serialVersionUID = 1L; // 2.5.0
protected final String _name;
protected final Version _version;
@@ -94,8 +92,11 @@
* use actual name and version number information.
*/
public SimpleModule() {
- // when passing 'this', can not chain constructors...
- _name = "SimpleModule-"+System.identityHashCode(this);
+ // can't chain when making reference to 'this'
+ // note: generate different name for direct instantiation, sub-classing
+ _name = (getClass() == SimpleModule.class) ?
+ "SimpleModule-"+System.identityHashCode(this)
+ : getClass().getName();
_version = Version.unknownVersion();
}
@@ -162,6 +163,19 @@
_serializers = new SimpleSerializers(serializers);
}
}
+
+ /**
+ * Since instances are likely to be custom, implementation returns
+ * <code>null</code> if (but only if!) this class is directly instantiated;
+ * but class name (default impl) for sub-classes.
+ */
+ @Override
+ public Object getTypeId() {
+ if (getClass() == SimpleModule.class) {
+ return null;
+ }
+ return super.getTypeId();
+ }
/*
/**********************************************************
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java b/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java
new file mode 100644
index 0000000..171b867
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java
@@ -0,0 +1,56 @@
+package com.fasterxml.jackson.databind.module;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.*;
+
+public class TestDuplicateRegistration extends BaseMapTest
+{
+ static class MyModule extends Module {
+ public int regCount;
+
+ public MyModule() {
+ super();
+ }
+
+ @Override
+ public String getModuleName() {
+ return "TestModule";
+ }
+
+ @Override
+ public Version version() {
+ return Version.unknownVersion();
+ }
+
+ @Override
+ public void setupModule(SetupContext context) {
+ ++regCount;
+ }
+ }
+
+ public void testDuplicateRegistration() throws Exception
+ {
+ // by default, duplicate registration should be prevented
+ ObjectMapper mapper = new ObjectMapper();
+ assertTrue(mapper.isEnabled(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS));
+ MyModule module = new MyModule();
+ mapper.registerModule(module);
+ mapper.registerModule(module);
+ mapper.registerModule(module);
+ assertEquals(1, module.regCount);
+
+ // but may be allowed by changing setting
+ mapper.disable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS);
+ mapper.registerModule(module);
+ assertEquals(2, module.regCount);
+
+ // and ditto for a new instance
+ ObjectMapper mapper2 = new ObjectMapper();
+ mapper2.disable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS);
+ MyModule module2 = new MyModule();
+ mapper.registerModule(module2);
+ mapper.registerModule(module2);
+ mapper.registerModule(module2);
+ assertEquals(3, module2.regCount);
+ }
+}