You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ju...@apache.org on 2016/11/17 01:56:59 UTC
svn commit: r1770102 - in /sling/trunk/bundles/extensions/models:
integration-tests/
integration-tests/src/main/java/org/apache/sling/models/it/exporter/
jackson-exporter/
jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/
jackson-...
Author: justin
Date: Thu Nov 17 01:56:59 2016
New Revision: 1770102
URL: http://svn.apache.org/viewvc?rev=1770102&view=rev
Log:
SLING-6295 - provide custom Jackson serialization mechanism for Resource objects
Added:
sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java
sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java
sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java
sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java
Modified:
sling/trunk/bundles/extensions/models/integration-tests/pom.xml
sling/trunk/bundles/extensions/models/integration-tests/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java
sling/trunk/bundles/extensions/models/jackson-exporter/pom.xml
sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java
Modified: sling/trunk/bundles/extensions/models/integration-tests/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/integration-tests/pom.xml?rev=1770102&r1=1770101&r2=1770102&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/models/integration-tests/pom.xml (original)
+++ sling/trunk/bundles/extensions/models/integration-tests/pom.xml Thu Nov 17 01:56:59 2016
@@ -293,7 +293,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.models.api</artifactId>
- <version>1.3.1-SNAPSHOT</version>
+ <version>1.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
Modified: sling/trunk/bundles/extensions/models/integration-tests/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/integration-tests/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java?rev=1770102&r1=1770101&r2=1770102&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/models/integration-tests/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java (original)
+++ sling/trunk/bundles/extensions/models/integration-tests/src/main/java/org/apache/sling/models/it/exporter/BaseComponent.java Thu Nov 17 01:56:59 2016
@@ -20,6 +20,7 @@ import javax.inject.Inject;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Exporter;
+import org.apache.sling.models.annotations.ExporterOption;
import org.apache.sling.models.annotations.Model;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -49,4 +50,8 @@ public class BaseComponent {
public String getSampleValueToUpperCase() {
return sampleValue.toUpperCase();
}
+
+ public Resource getResource() {
+ return resource;
+ }
}
Modified: sling/trunk/bundles/extensions/models/jackson-exporter/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/jackson-exporter/pom.xml?rev=1770102&r1=1770101&r2=1770102&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/models/jackson-exporter/pom.xml (original)
+++ sling/trunk/bundles/extensions/models/jackson-exporter/pom.xml Thu Nov 17 01:56:59 2016
@@ -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 -->
<!-- *************************************************************** -->
Added: sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java?rev=1770102&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java (added)
+++ sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/ModuleProvider.java Thu Nov 17 01:56:59 2016
@@ -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();
+}
Modified: sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java?rev=1770102&r1=1770101&r2=1770102&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java (original)
+++ sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporter.java Thu Nov 17 01:56:59 2016
@@ -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.Serial
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
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
}
}
}
+ 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
}
}
+ 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";
Added: sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java?rev=1770102&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java (added)
+++ sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceModelProvider.java Thu Nov 17 01:56:59 2016
@@ -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;
+ }
+
+}
Added: sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java?rev=1770102&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java (added)
+++ sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceSerializer.java Thu Nov 17 01:56:59 2016
@@ -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);
+ }
+}
Added: sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java?rev=1770102&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java (added)
+++ sling/trunk/bundles/extensions/models/jackson-exporter/src/main/java/org/apache/sling/models/jacksonexporter/package-info.java Thu Nov 17 01:56:59 2016
@@ -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