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