Skip to content

Commit

Permalink
Test mapper factory change
Browse files Browse the repository at this point in the history
  • Loading branch information
kqarryzada committed Jul 19, 2024
1 parent 6b943ed commit cbd9125
Show file tree
Hide file tree
Showing 3 changed files with 360 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.unboundid.scim2.common.annotations.NotNull;

Expand All @@ -32,29 +33,97 @@
import java.util.Date;
import java.util.Map;

import static com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES;

/**
* Class used to customize the object mapper that is used by the SCIM 2 SDK.
* This class may be used to customize the object mapper that is used by the
* SCIM SDK.
* <br><br>
*
* The SCIM SDK uses a Jackson {@link ObjectMapper} to convert SCIM resources
* between JSON strings and Plain Old Java Objects such as
* {@link com.unboundid.scim2.common.types.UserResource}. This object mapper is
* configured with specific settings to benefit applications that use the SCIM
* SDK. For example, when converting a Java object to a JSON string, the SCIM
* SDK will ignore {@code null} fields from the object.
* <br><br>
*
* If your project would benefit from enabling or disabling certain Jackson
* features on the SCIM SDK's object mapper, use one of the following methods:
* <ul>
* <li> {@link #setMapperCustomFeatures}
* <li> {@link #setDeserializationCustomFeatures}
* <li> {@link #setSerializationCustomFeatures}
* <li> {@link #setJsonParserCustomFeatures}
* <li> {@link #setJsonGeneratorCustomFeatures}
* </ul>
*
* For example, to disable the
* {@link MapperFeature#ACCEPT_CASE_INSENSITIVE_PROPERTIES} property, use the
* following Java code:
* <pre>
* MapperFactory newFactory = new MapperFactory();
* newFactory.setMapperCustomFeatures(
* Map.of(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, false)
* );
*
* // Register the new MapperFactory with the SCIM SDK.
* JsonUtils.setCustomMapperFactory(newFactory);
* </pre>
*
* If your desired customization is more complicated than enabling/disabling
* Jackson features, an alternative is to create a custom {@code MapperFactory}
* class that overrides the behavior of {@link #createObjectMapper}. When
* overriding this method, the subclass <em>must</em> first fetch an object
* mapper from the superclass to ensure that the SCIM SDK's original
* configuration is preserved. For example:
* <pre>
* public class CustomMapperFactory extends MapperFactory
* {
* {@literal @}Override
* public ObjectMapper createObjectMapper()
* {
* // Fetch the initial object mapper from the superclass, then add your
* // customizations. Do not instantiate a new ObjectMapper.
* ObjectMapper mapper = super.createObjectMapper();
*
* // Add the desired customizations.
* SimpleModule module = new SimpleModule();
* module.addSerializer(DesiredClass.class, new CustomSerializer());
* module.addDeserializer(DesiredClass.class, new CustomDeserializer());
* mapper.registerModule(module);
*
* return mapper;
* }
* }
* </pre>
*
* When your application starts up, register your customer mapper factory with
* the SCIM SDK to use the object mapper returned by the custom class:
* <pre>
* JsonUtils.setCustomMapperFactory(new CustomMapperFactory());
* </pre>
*/
public class MapperFactory
{
@NotNull
private static Map<DeserializationFeature, Boolean> deserializationCustomFeatures =
private Map<DeserializationFeature, Boolean> deserializationCustomFeatures =
Collections.emptyMap();

@NotNull
private static Map<JsonParser.Feature, Boolean> jsonParserCustomFeatures =
private Map<JsonParser.Feature, Boolean> jsonParserCustomFeatures =
Collections.emptyMap();

@NotNull
private static Map<JsonGenerator.Feature, Boolean> jsonGeneratorCustomFeatures =
private Map<JsonGenerator.Feature, Boolean> jsonGeneratorCustomFeatures =
Collections.emptyMap();

@NotNull
private static Map<MapperFeature, Boolean> mapperCustomFeatures =
private Map<MapperFeature, Boolean> mapperCustomFeatures =
Collections.emptyMap();

@NotNull
private static Map<SerializationFeature, Boolean> serializationCustomFeatures =
private Map<SerializationFeature, Boolean> serializationCustomFeatures =
Collections.emptyMap();

/**
Expand Down Expand Up @@ -152,9 +221,20 @@ public MapperFactory setSerializationCustomFeatures(
* and deserializing SCIM JSON objects.
*/
@NotNull
public static ObjectMapper createObjectMapper()
public ObjectMapper createObjectMapper()
{
ObjectMapper mapper = new ObjectMapper(new ScimJsonFactory());
// Create a new object mapper with case-insensitive settings.
var objectMapperBuilder = JsonMapper.builder(new ScimJsonFactory());

// Do not care about case when de-serializing POJOs.
objectMapperBuilder.enable(ACCEPT_CASE_INSENSITIVE_PROPERTIES);

// Add any custom mapper features. This must be done before other fields
// (e.g., serializationCustomFeatures) because it must be configured on the
// builder object.
mapperCustomFeatures.forEach(objectMapperBuilder::configure);

final ObjectMapper mapper = objectMapperBuilder.build();

// Don't serialize POJO nulls as JSON nulls.
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Expand All @@ -168,36 +248,16 @@ public static ObjectMapper createObjectMapper()
dateTimeModule.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(dateTimeModule);

// Do not care about case when de-serializing POJOs.
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);

// Use the case-insensitive JsonNodes.
mapper.setNodeFactory(new ScimJsonNodeFactory());

for (DeserializationFeature feature : deserializationCustomFeatures.keySet())
{
mapper.configure(feature, deserializationCustomFeatures.get(feature));
}

for (JsonGenerator.Feature feature : jsonGeneratorCustomFeatures.keySet())
{
mapper.configure(feature, jsonGeneratorCustomFeatures.get(feature));
}

for (JsonParser.Feature feature : jsonParserCustomFeatures.keySet())
{
mapper.configure(feature, jsonParserCustomFeatures.get(feature));
}

for (MapperFeature feature : mapperCustomFeatures.keySet())
{
mapper.configure(feature, mapperCustomFeatures.get(feature));
}

for (SerializationFeature feature : serializationCustomFeatures.keySet())
{
mapper.configure(feature, serializationCustomFeatures.get(feature));
}
// Configure the custom Jackson features for object mappers created and used
// by the SCIM SDK. This step is performed last to ensure that
// customizations are not overwritten.
deserializationCustomFeatures.forEach(mapper::configure);
jsonGeneratorCustomFeatures.forEach(mapper::configure);
jsonParserCustomFeatures.forEach(mapper::configure);
serializationCustomFeatures.forEach(mapper::configure);

return mapper;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;
import com.unboundid.scim2.common.exceptions.ScimException;
import com.unboundid.scim2.common.filters.Filter;
import com.unboundid.scim2.common.types.Name;
import com.unboundid.scim2.common.utils.DateTimeUtils;
import com.unboundid.scim2.common.utils.JsonUtils;
import com.unboundid.scim2.common.utils.MapperFactory;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -1180,38 +1176,6 @@ public Object[][] getPathExistsParams() throws Exception
};
}

/**
* Test that setting a custom object mapper factory allows for custom
* options such as setting fail on unknown properties deserialization option
* to false (defaults to true).
*
* @throws Exception if an error occurs.
*/
@Test
public void testCustomMapper() throws Exception
{
MapperFactory mapperFactory = new MapperFactory();
mapperFactory.setDeserializationCustomFeatures(
ImmutableMap.<DeserializationFeature, Boolean>builder().
put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, Boolean.FALSE).build());
JsonUtils.setCustomMapperFactory(mapperFactory);

String jsonNameString =
"{" +
"\"familyName\":\"Smith\"," +
"\"givenName\":\"Bob\"," +
"\"middleName\":\"X\"," +
"\"bogusField\":\"bogusValue\"" +
"}";

Name name = JsonUtils.getObjectReader().
forType(Name.class).readValue(jsonNameString);

Assert.assertEquals(name.getFamilyName(), "Smith");
Assert.assertEquals(name.getGivenName(), "Bob");
Assert.assertEquals(name.getMiddleName(), "X");
}

/**
* Test that the SCIM 2 SDK ObjectMapper ignores null map values.
*/
Expand Down
Loading

0 comments on commit cbd9125

Please sign in to comment.