You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:55:39 UTC
[sling-org-apache-sling-models-jacksonexporter] 03/07: SLING-6295 -
provide custom Jackson serialization mechanism for Resource objects
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to annotated tag org.apache.sling.models.jacksonexporter-1.0.2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-jacksonexporter.git
commit 82f5c85d459de780a2500da4ee08ded3d90d0059
Author: Justin Edelson <ju...@apache.org>
AuthorDate: Thu Nov 17 01:56:59 2016 +0000
SLING-6295 - provide custom Jackson serialization mechanism for Resource objects
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/jackson-exporter@1770102 13f79535-47bb-0310-9956-ffa450edef68
---
pom.xml | 13 ++
.../models/jacksonexporter/ModuleProvider.java | 30 +++
.../jacksonexporter/impl/JacksonExporter.java | 25 +++
.../impl/ResourceModelProvider.java | 64 +++++++
.../jacksonexporter/impl/ResourceSerializer.java | 211 +++++++++++++++++++++
.../sling/models/jacksonexporter/package-info.java | 21 ++
6 files changed, 364 insertions(+)
diff --git a/pom.xml b/pom.xml
index ae2473b..c1a9287 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,7 @@
<configuration>
<instructions>
<Embed-Dependency>*;scope=compile</Embed-Dependency>
+ <Conditional-Package>org.apache.sling.commons.osgi</Conditional-Package>
</instructions>
</configuration>
</plugin>
@@ -102,6 +103,18 @@
<artifactId>commons-lang</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.4.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.4.0</version>
+ <scope>provided</scope>
+ </dependency>
<!-- *************************************************************** -->
<!-- JACKSON -->
<!-- *************************************************************** -->
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java b/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java
new file mode 100644
index 0000000..01d9b0a
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.models.jacksonexporter;
+
+import aQute.bnd.annotation.ConsumerType;
+import com.fasterxml.jackson.databind.Module;
+
+/**
+ * Extension interface which allows for plugging in Jackson Modules
+ * into the Jackson Exporter
+ */
+@ConsumerType
+public interface ModuleProvider {
+
+ Module getModule();
+}
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java b/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java
index 71492e1..8c149ad 100644
--- a/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java
@@ -25,7 +25,12 @@ import java.util.Map;
import com.fasterxml.jackson.databind.MapperFeature;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.Order;
+import org.apache.sling.commons.osgi.RankedServices;
import org.apache.sling.models.export.spi.ModelExporter;
import org.apache.sling.models.factory.ExportException;
@@ -35,9 +40,14 @@ import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
+import org.apache.sling.models.jacksonexporter.ModuleProvider;
+import org.apache.sling.models.spi.Injector;
+import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nonnull;
+
@Component
@Service
public class JacksonExporter implements ModelExporter {
@@ -52,6 +62,10 @@ public class JacksonExporter implements ModelExporter {
private static final int MAPPER_FEATURE_PREFIX_LENGTH = MAPPER_FEATURE_PREFIX.length();
+ @Reference(name = "moduleProvider", referenceInterface = ModuleProvider.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ private final @Nonnull RankedServices<ModuleProvider> moduleProviders = new RankedServices<ModuleProvider>(Order.ASCENDING);
+
@Override
public boolean isSupported(Class<?> clazz) {
return clazz.equals(String.class) || clazz.equals(Map.class);
@@ -81,6 +95,9 @@ public class JacksonExporter implements ModelExporter {
}
}
}
+ for (ModuleProvider moduleProvider : moduleProviders) {
+ mapper.registerModule(moduleProvider.getModule());
+ }
if (clazz.equals(Map.class)) {
return (T) mapper.convertValue(model, Map.class);
@@ -111,6 +128,14 @@ public class JacksonExporter implements ModelExporter {
}
}
+ protected void bindModuleProvider(final ModuleProvider moduleProvider, final Map<String, Object> props) {
+ moduleProviders.bind(moduleProvider, props);
+ }
+
+ protected void unbindModuleProvider(final ModuleProvider moduleProvider, final Map<String, Object> props) {
+ moduleProviders.unbind(moduleProvider, props);
+ }
+
@Override
public String getName() {
return "jackson";
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java
new file mode 100644
index 0000000..43fa303
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.models.jacksonexporter.impl;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.module.SimpleSerializers;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.models.jacksonexporter.ModuleProvider;
+import org.osgi.framework.Constants;
+
+import java.util.Map;
+
+@Component(metatype = true, label = "Apache Sling Models Jackson Exporter - Resource object support",
+ description = "Provider of a Jackson Module which enables support for proper serialization of Resource objects")
+@Service
+@Property(name = Constants.SERVICE_RANKING, intValue = 0, propertyPrivate = true)
+public class ResourceModelProvider implements ModuleProvider {
+
+ private static final int DEFAULT_MAX_RECURSION_LEVELS = -1;
+
+ @Property(label = "Maximum Recursion Levels",
+ description = "Maximum number of levels of child resources which will be exported for each resource. Specify -1 for infinite.",
+ intValue = DEFAULT_MAX_RECURSION_LEVELS)
+ private static final String PROP_MAX_RECURSION_LEVELS = "max.recursion.levels";
+
+ private int maxRecursionLevels;
+ private SimpleModule moduleInstance;
+
+ @Activate
+ private void activate(Map<String, Object> props) {
+ this.maxRecursionLevels = PropertiesUtil.toInteger(props.get(PROP_MAX_RECURSION_LEVELS), DEFAULT_MAX_RECURSION_LEVELS);
+ this.moduleInstance = new SimpleModule();
+ SimpleSerializers serializers = new SimpleSerializers();
+ serializers.addSerializer(Resource.class, new ResourceSerializer(maxRecursionLevels));
+ moduleInstance.setSerializers(serializers);
+
+ }
+
+ @Override
+ public Module getModule() {
+ return moduleInstance;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java
new file mode 100644
index 0000000..72bab6a
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.models.jacksonexporter.impl;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.Map;
+
+import static javax.xml.bind.JAXBIntrospector.getValue;
+
+public class ResourceSerializer extends JsonSerializer<Resource> implements ResolvableSerializer {
+
+ private final int maxRecursionLevels;
+ private JsonSerializer<Object> calendarSerializer;
+
+ public ResourceSerializer(int maxRecursionLevels) {
+ this.maxRecursionLevels = maxRecursionLevels;
+ }
+
+ @Override
+ public void serialize(final Resource value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
+ create(value, jgen, 0, provider);
+ }
+
+ /** Dump given resource in JSON, optionally recursing into its objects */
+ private void create(final Resource resource, final JsonGenerator jgen, final int currentRecursionLevel,
+ final SerializerProvider provider) throws IOException {
+ jgen.writeStartObject();
+
+ final ValueMap valueMap = resource.adaptTo(ValueMap.class);
+
+ final Map propertyMap = (valueMap != null) ? valueMap : resource.adaptTo(Map.class);
+
+ if (propertyMap == null) {
+
+ // no map available, try string
+ final String value = resource.adaptTo(String.class);
+ if (value != null) {
+
+ // single value property or just plain String resource or...
+ jgen.writeStringField(resource.getName(), value);
+
+ } else {
+
+ // Try multi-value "property"
+ final String[] values = resource.adaptTo(String[].class);
+ if (values != null) {
+ jgen.writeArrayFieldStart(resource.getName());
+ for (final String s : values) {
+ jgen.writeString(s);
+ }
+ jgen.writeEndArray();
+ }
+
+ }
+
+ } else {
+
+ @SuppressWarnings("unchecked")
+ final Iterator<Map.Entry> props = propertyMap.entrySet().iterator();
+
+ // the node's actual properties
+ while (props.hasNext()) {
+ final Map.Entry prop = props.next();
+
+ if (prop.getValue() != null) {
+ createProperty(jgen, valueMap, prop.getKey().toString(), prop.getValue(), provider);
+ }
+ }
+ }
+
+ // the child nodes
+ if (recursionLevelActive(currentRecursionLevel)) {
+ for (final Resource n : resource.getChildren()) {
+ jgen.writeObjectFieldStart(n.getName());
+ create(n, jgen, currentRecursionLevel + 1, provider);
+ }
+ }
+
+ jgen.writeEndObject();
+ }
+
+ /**
+ * Write a single property
+ */
+ private void createProperty(final JsonGenerator jgen, final ValueMap valueMap, final String key, final Object value,
+ final SerializerProvider provider)
+ throws IOException {
+ Object[] values = null;
+ if (value.getClass().isArray()) {
+ final int length = Array.getLength(value);
+ // write out empty array
+ if ( length == 0 ) {
+ jgen.writeArrayFieldStart(key);
+ jgen.writeEndArray();
+ return;
+ }
+ values = new Object[Array.getLength(value)];
+ for(int i=0; i<length; i++) {
+ values[i] = Array.get(value, i);
+ }
+ }
+
+ // special handling for binaries: we dump the length and not the data!
+ if (value instanceof InputStream
+ || (values != null && values[0] instanceof InputStream)) {
+ // TODO for now we mark binary properties with an initial colon in
+ // their name
+ // (colon is not allowed as a JCR property name)
+ // in the name, and the value should be the size of the binary data
+ if (values == null) {
+ jgen.writeNumberField(":" + key, getLength(valueMap, -1, key, (InputStream)value));
+ } else {
+ jgen.writeArrayFieldStart(":" + key);
+ for (int i = 0; i < values.length; i++) {
+ jgen.writeNumber(getLength(valueMap, i, key, (InputStream)values[i]));
+ }
+ jgen.writeEndArray();
+ }
+ return;
+ }
+
+ if (!value.getClass().isArray()) {
+ jgen.writeFieldName(key);
+ writeValue(jgen, value, provider);
+ } else {
+ jgen.writeArrayFieldStart(key);
+ for (Object v : values) {
+ writeValue(jgen, v, provider);
+ }
+ jgen.writeEndArray();
+ }
+ }
+
+ /** true if the current recursion level is active */
+ private boolean recursionLevelActive(final int currentRecursionLevel) {
+ return maxRecursionLevels < 0 || currentRecursionLevel < maxRecursionLevels;
+ }
+
+ private long getLength(final ValueMap valueMap, final int index, final String key, final InputStream stream) {
+ try {
+ stream.close();
+ } catch (IOException ignore) {}
+
+ long length = -1;
+ if ( valueMap != null ) {
+ if ( index == -1 ) {
+ length = valueMap.get(key, length);
+ } else {
+ Long[] lengths = valueMap.get(key, Long[].class);
+ if ( lengths != null && lengths.length > index ) {
+ length = lengths[index];
+ }
+ }
+ }
+ return length;
+ }
+
+ /** Dump only a value in the correct format */
+ private void writeValue(final JsonGenerator jgen, final Object value, final SerializerProvider provider) throws IOException {
+ if (value instanceof InputStream) {
+ // input stream is already handled
+ jgen.writeNumber(0);
+ } else if (value instanceof Calendar) {
+ calendarSerializer.serialize(value, jgen, provider);
+ } else if (value instanceof Boolean) {
+ jgen.writeBoolean(((Boolean)value).booleanValue());
+ } else if (value instanceof Long) {
+ jgen.writeNumber(((Long)value).longValue());
+ } else if (value instanceof Integer) {
+ jgen.writeNumber(((Integer)value).intValue());
+ } else if (value instanceof Double) {
+ jgen.writeNumber(((Double)value).doubleValue());
+ } else if (value != null) {
+ jgen.writeString(value.toString());
+ } else {
+ jgen.writeString(""); // assume empty string
+ }
+ }
+
+ @Override
+ public void resolve(SerializerProvider provider) throws JsonMappingException {
+ this.calendarSerializer = provider.findValueSerializer(Calendar.class, null);
+ }
+}
diff --git a/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java b/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java
new file mode 100644
index 0000000..58da044
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.models.jacksonexporter;
+
+import aQute.bnd.annotation.Version;
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.