diff --git a/pom.xml b/pom.xml
index 5b8b5488..ec098653 100644
--- a/pom.xml
+++ b/pom.xml
@@ -110,7 +110,7 @@
org.apache.sling
org.apache.sling.models.api
- 1.3.6
+ 1.3.9-SNAPSHOT
provided
@@ -217,5 +217,10 @@
1.0.8
test
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.3.2
+
diff --git a/src/main/java/org/apache/sling/models/impl/serializer/DefaultExternalizePathProvider.java b/src/main/java/org/apache/sling/models/impl/serializer/DefaultExternalizePathProvider.java
new file mode 100644
index 00000000..9a97ec38
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/serializer/DefaultExternalizePathProvider.java
@@ -0,0 +1,111 @@
+/*
+ * 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.impl.serializer;
+
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.annotations.ExternalizePath;
+import org.apache.sling.models.annotations.ExternalizePathProvider;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Component;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/** Fallback Implementation of the Externalized Path Provider that uses the Resource Resolver's map function **/
+@Component(
+ property = Constants.SERVICE_RANKING + ":Integer=1",
+ immediate = true,
+ service = {
+ ExternalizePathProvider.class
+ }
+)
+public class DefaultExternalizePathProvider
+ implements ExternalizePathProvider
+{
+ @Override
+ public String externalize(@NotNull Object model, ExternalizePath annotation, String sourcePath) {
+ String answer = sourcePath;
+ ResourceResolver resourceResolver = getResourceResolver(model, annotation);
+ if (sourcePath != null && !sourcePath.isEmpty() && resourceResolver != null) {
+ answer = resourceResolver.map(sourcePath);
+ }
+ return answer;
+ }
+
+ /**
+ * Obtains the Resource from the Model in order to Externalize
+ * @param model
+ * @param annotation
+ * @return
+ */
+ private ResourceResolver getResourceResolver(Object model, ExternalizePath annotation) {
+ Resource answer = null;
+ // Get Resource from specified Resource method
+ String resourceMethodName = annotation.resourceMethod();
+ if(!resourceMethodName.isEmpty()) {
+ try {
+ Method getResourceMethod = model.getClass().getMethod(resourceMethodName);
+ if(getResourceMethod.getReturnType().isAssignableFrom(Resource.class)) {
+ answer = (Resource) getResourceMethod.invoke(model, null);
+ }
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ // If not found then we cannot use Externalize Path but then we just send the original value
+ }
+ }
+ if(answer == null) {
+ // Get Resource from specified Resource Field
+ String resourceFieldName = annotation.resourceField();
+ if (!resourceFieldName.isEmpty()) {
+ try {
+ Field resourceField = FieldUtils.getField(model.getClass(), resourceFieldName, true);
+ if(resourceField != null && resourceField.getType().isAssignableFrom(Resource.class)) {
+ answer = (Resource) resourceField.get(model);
+ }
+ } catch (IllegalAccessException e) {
+ // If not found then we cannot use Externalize Path but then we just send the original value
+ }
+ }
+ }
+ if(answer == null) {
+ // Get Resource from default location (getResource())
+ try {
+ Method getResourceMethod = model.getClass().getMethod("getResource");
+ if(getResourceMethod.getReturnType().isAssignableFrom(Resource.class)) {
+ answer = (Resource) getResourceMethod.invoke(model, null);
+ }
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ // If not found then we cannot use Externalize Path but then we just send the original value
+ }
+ }
+ if(answer == null) {
+ // Get Resource from default Resource Field (resource)
+ try {
+ Field resourceField = FieldUtils.getField(model.getClass(), "resource", true);
+ if(resourceField != null && resourceField.getType().isAssignableFrom(Resource.class)) {
+ answer = (Resource) resourceField.get(model);
+ }
+ } catch (IllegalAccessException e) {
+ // If not found then we cannot use Externalize Path but then we just send the original value
+ }
+ }
+ return answer == null ? null : answer.getResourceResolver();
+ }
+}
diff --git a/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathProviderManager.java b/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathProviderManager.java
new file mode 100644
index 00000000..86e4202c
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathProviderManager.java
@@ -0,0 +1,27 @@
+/*
+ * 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.impl.serializer;
+
+import org.apache.sling.models.annotations.ExternalizePathProvider;
+
+/** Service that maintains a list of All Externalize Path Providers and returns the best fitting one depending on its implementation **/
+public interface ExternalizePathProviderManager {
+
+ /** @return The best fitting Provider that is managed here at the moment **/
+ ExternalizePathProvider getExternalizedPathProvider();
+
+}
diff --git a/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathProviderManagerService.java b/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathProviderManagerService.java
new file mode 100644
index 00000000..ca3ba45d
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathProviderManagerService.java
@@ -0,0 +1,60 @@
+/*
+ * 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.impl.serializer;
+
+import org.apache.sling.commons.osgi.Order;
+import org.apache.sling.commons.osgi.RankedServices;
+import org.apache.sling.models.annotations.ExternalizePathProvider;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+
+import java.util.Map;
+
+/**
+ * Simple Implementation of the Externalize Path Provider Manager service
+ * which just binds them and then selects the highest one (first one as the
+ * order is descending).
+ */
+@Component(
+ service={
+ ExternalizePathProviderManager.class
+ }
+)
+public class ExternalizePathProviderManagerService
+ implements ExternalizePathProviderManager
+{
+ private RankedServices providers = new RankedServices<>(Order.DESCENDING);
+
+ @Reference(
+ cardinality = ReferenceCardinality.MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ )
+ protected void bindExternalizePathProvider(final ExternalizePathProvider provider, final Map props) {
+ providers.bind(provider, props);
+ }
+
+ protected void unbindExternalizePathProvider(final ExternalizePathProvider provider, final Map props) {
+ providers.unbind(provider, props);
+ }
+
+ @Override
+ public ExternalizePathProvider getExternalizedPathProvider() {
+ return providers.getList().get(0);
+ }
+}
diff --git a/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathSerializer.java b/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathSerializer.java
new file mode 100644
index 00000000..76ffd997
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/serializer/ExternalizePathSerializer.java
@@ -0,0 +1,209 @@
+/*
+ * 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.impl.serializer;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.sling.models.annotations.ExternalizePath;
+import org.apache.sling.models.annotations.ExternalizePathProvider;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Json Serializer that will take 'Externalize Path' annotation and shortens them
+ * with the current Externalize Path Provider. This Serializer is used as an Annotation
+ * on the Model:
+ *
+ * @Exporter(name = "jackson", extensions = "json")
+ * @JsonSerialize(using = ExternalizePathSerializer.class)
+ *
+ * ATTENTION: this class is no an OSGi class but it needs to obtain a service to the
+ * {@link ExternalizePathProviderManager} and so this class can only be used in an OSGi
+ * environment. There is also some restriction with respect to the Providers as they need
+ * access to other services like the Resource Resolver.
+ */
+public class ExternalizePathSerializer
+ extends JsonSerializer
+{
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ private ExternalizePathProviderManager externalizePathProviderManager =
+ getService(ExternalizePathProviderManager.class, ExternalizePathProviderManager.class);
+
+ @Override
+ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
+ throws IOException
+ {
+ jgen.writeStartObject();
+ try {
+ if(value != null) {
+ Class valueClass = value.getClass();
+ // List all public methods (source of the Model)
+ Method[] methods = value.getClass().getMethods();
+ for(Method method: methods) {
+ // Ignore methods on Object class
+ if(method.getDeclaringClass() != Object.class) {
+ // Get Method Name, check if Method is Json Ignored and check that method is a Getter
+ String methodName = method.getName();
+ if(method.getAnnotation(JsonIgnore.class) != null) {
+ logger.debug("Ignore Method because of JsonIgnore Annotation: '{}'", methodName);
+ }
+ if ((methodName.startsWith("get") || methodName.startsWith("is")) && method.getParameterTypes().length == 0) {
+ Object property;
+ try {
+ // Obtain Value from method and get corresponding Field Name
+ property = method.invoke(value, null);
+ String fieldName = null;
+ if (methodName.startsWith("get")) {
+ fieldName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
+ } else if (methodName.startsWith("is")) {
+ fieldName = methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
+ }
+ if (property == null) {
+ // If Property is null then write out a NULL
+ jgen.writeNullField(fieldName);
+ } else {
+ // Try to get the Annotation (Method or Field)
+ ExternalizePath externalizePath = method.getAnnotation(ExternalizePath.class);
+ if(externalizePath == null) {
+ // If method does not have the Externalize Path Annotation then check its corresponding Field
+ Field propertyField = FieldUtils.getField(valueClass, fieldName, true);
+ if(propertyField != null) {
+ // Check type
+ Class fieldType = propertyField.getType();
+ Class methodType = method.getReturnType();
+ if (!fieldType.isAssignableFrom(methodType)) {
+ logger.warn("Matching Field: '{}' is not assignable to method: '{}', ignore Annotation", fieldName, methodName);
+ } else {
+ externalizePath = propertyField.getAnnotation(ExternalizePath.class);
+ }
+ }
+ }
+ if(externalizePath != null) {
+ // Enforce that this Annotation only works Strings and if so get the Externalization
+ // Provider and if found externalize it
+ if(!(property instanceof String)) {
+ logger.warn(
+ "Annotation 'Externalize Path' can only be applied to a String but was applied to: '{}'",
+ method.getReturnType().getName()
+ );
+ } else {
+ // If this method is Externalize Path then map the value first
+ ExternalizePathProvider externalizePathProvider = getExternalizedPathProvider();
+ if (externalizePathProvider != null) {
+ property = externalizePathProvider.externalize(value, externalizePath, (String) property);
+ }
+ }
+ }
+ // Write Property out
+ createProperty(jgen, fieldName, property, provider);
+ }
+ } catch (InvocationTargetException | RuntimeException e) {
+ logger.warn("Failed to Invoke Method: '{} -> ignored", e.getLocalizedMessage());
+ }
+ }
+ }
+ }
+ }
+ } catch(JsonProcessingException | RuntimeException e) {
+ logger.warn("Externalize Path Serialize failed", e);
+ } catch (IllegalAccessException e) {
+ logger.warn("Externalize Path Method Access failed", e);
+ } finally {
+ jgen.writeEndObject();
+ }
+ }
+
+ void createProperty(final JsonGenerator jgen, final String name, 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(name);
+ jgen.writeEndArray();
+ return;
+ }
+ values = new Object[Array.getLength(value)];
+ for(int i=0; i T getService(Class clazz, Class type) {
+ Bundle currentBundle = FrameworkUtil.getBundle(clazz);
+ if (currentBundle == null) {
+ return null;
+ }
+ BundleContext bundleContext = currentBundle.getBundleContext();
+ if (bundleContext == null) {
+ return null;
+ }
+ ServiceReference serviceReference = bundleContext.getServiceReference(type);
+ if (serviceReference == null) {
+ return null;
+ }
+ T service = bundleContext.getService(serviceReference);
+ if (service == null) {
+ return null;
+ }
+ return service;
+ }
+}
diff --git a/src/test/java/org/apache/sling/models/impl/serializer/ExternalizedPathSerializerTest.java b/src/test/java/org/apache/sling/models/impl/serializer/ExternalizedPathSerializerTest.java
new file mode 100644
index 00000000..a9dcc837
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/serializer/ExternalizedPathSerializerTest.java
@@ -0,0 +1,436 @@
+/*
+ * 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.impl.serializer;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.annotations.ExternalizePath;
+import org.apache.sling.models.annotations.ExternalizePathProvider;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.internal.util.reflection.Whitebox;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.osgi.framework.Constants.SERVICE_ID;
+import static org.osgi.framework.Constants.SERVICE_RANKING;
+
+public class ExternalizedPathSerializerTest {
+
+ private ExternalizePathSerializer externalizePathSerializer;
+ private ExternalizePathProviderManagerService externalizePathProviderManagerService;
+ private Resource resource;
+ private ResourceResolver resourceResolver;
+ private JsonGenerator jsonGenerator;
+ private SerializerProvider serializerProvider;
+
+ @Before
+ public void setup() {
+ externalizePathSerializer = spy(new ExternalizePathSerializer());
+ externalizePathProviderManagerService = new ExternalizePathProviderManagerService();
+ ExternalizePathProvider defaultProvider = new DefaultExternalizePathProvider();
+ Map props = new HashMap<>();
+ props.put(SERVICE_ID, 1L);
+ props.put(SERVICE_RANKING, 1);
+ externalizePathProviderManagerService.bindExternalizePathProvider(defaultProvider, props);
+
+ resource = mock(Resource.class);
+ jsonGenerator = mock(JsonGenerator.class);
+ serializerProvider = mock(SerializerProvider.class);
+ resourceResolver = mock(ResourceResolver.class);
+ when(resource.getResourceResolver()).thenReturn(resourceResolver);
+ }
+
+ @Test
+ public void testNoSerialization() throws Exception {
+ final String imagePath = "/content/test/image/test-image.jpg";
+ final String name = "imagePath";
+
+ NoAnnotationModel model = new NoAnnotationModel(resource, imagePath);
+ when(resourceResolver.map(imagePath)).thenReturn(imagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertEquals("Image Path should not have changed", imagePath, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ @Test
+ public void testSimpleSerialization() throws Exception {
+ final String imagePath = "/content/test/image/test-image.jpg";
+ final String mappedImagePath = "/image/test-image.jpg";
+ final String name = "imagePath";
+
+ Whitebox.setInternalState(
+ externalizePathSerializer, "externalizePathProviderManager", externalizePathProviderManagerService
+ );
+ MethodAnnotatedModel model = new MethodAnnotatedModel(resource, imagePath);
+ when(resourceResolver.map(imagePath)).thenReturn(mappedImagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertNotEquals("Image Path should have changed", imagePath, stringValue);
+ assertEquals("Image Path did not change as expected", mappedImagePath, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ @Test
+ public void testCustomProviderInjection() throws Exception {
+ String imagePath = "/content/test/image/test-image.jpg";
+ String from = "/content/test/image/";
+ String to1 = "/image1/";
+ String to2 = "/image2/";
+ String to3 = "/image3/";
+ String mappedImagePath = "/image/test-image.jpg";
+ String mappedImagePath3 = "/image3/test-image.jpg";
+ String name = "imagePath";
+
+ when(resourceResolver.map(imagePath)).thenReturn(mappedImagePath);
+
+ TestExternalizePathProvider provider1 = new TestExternalizePathProvider(from, to1);
+ // ATTENTION: Properties Map need to be reset as they are stored into the RankedServices as is
+ Map props = new HashMap<>();
+ props.put(SERVICE_ID, 1234L);
+ props.put(SERVICE_RANKING, 100);
+ externalizePathProviderManagerService.bindExternalizePathProvider(provider1, props);
+ TestExternalizePathProvider provider3 = new TestExternalizePathProvider(from, to3);
+ props = new HashMap<>();
+ props.put(SERVICE_ID, 1235L);
+ props.put(SERVICE_RANKING, 400);
+ externalizePathProviderManagerService.bindExternalizePathProvider(provider3, props);
+ TestExternalizePathProvider provider2 = new TestExternalizePathProvider(from, to2);
+ props = new HashMap<>();
+ props.put(SERVICE_ID, 1236L);
+ props.put(SERVICE_RANKING, 200);
+ externalizePathProviderManagerService.bindExternalizePathProvider(provider2, props);
+
+ Whitebox.setInternalState(
+ externalizePathSerializer, "externalizePathProviderManager", externalizePathProviderManagerService
+ );
+ MethodAnnotatedModel model = new MethodAnnotatedModel(resource, imagePath);
+ when(resourceResolver.map(imagePath)).thenReturn(mappedImagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertNotEquals("Image Path should have changed", imagePath, stringValue);
+ assertEquals("Image Path did not change as expected", mappedImagePath3, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ @Test
+ public void testAnnotationInInheritanceSerialization() throws Exception {
+ final String imagePath = "/content/test/image/test-image.jpg";
+ final String testPath = "/content/testTest/image/test-image.jpg";
+ final String mappedImagePath = "/image/test-image.jpg";
+ final String mappedTestImagePath = "/image/test/test-image.jpg";
+ final String name = "imagePath";
+ final String testName = "testPath";
+
+ Whitebox.setInternalState(
+ externalizePathSerializer, "externalizePathProviderManager", externalizePathProviderManagerService
+ );
+ MethodAnnotatedInInheritanceModel model = new MethodAnnotatedInInheritanceModel(resource, imagePath, testPath);
+ when(resourceResolver.map(eq(imagePath))).thenReturn(mappedImagePath);
+ when(resourceResolver.map(eq(testPath))).thenReturn(mappedTestImagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertNotEquals("Image Path should have changed", imagePath, stringValue);
+ assertEquals("Image Path did not change as expected", mappedImagePath, stringValue);
+ } else if(fieldName.equals(testName)) {
+ String stringValue = (String) value;
+ assertNotEquals("Test Path should have changed", testPath, stringValue);
+ assertEquals("Test Path did not change as expected", mappedTestImagePath, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ @Test
+ public void testResourceMethod() throws Exception {
+ final String imagePath = "/content/test/image/test-image.jpg";
+ final String mappedImagePath = "/image/test-image.jpg";
+ final String name = "imagePath";
+
+ Whitebox.setInternalState(
+ externalizePathSerializer, "externalizePathProviderManager", externalizePathProviderManagerService
+ );
+ AnnotationResourceMethodModel model = new AnnotationResourceMethodModel(resource, imagePath);
+ when(resourceResolver.map(eq(imagePath))).thenReturn(mappedImagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertNotEquals("Image Path should have changed", imagePath, stringValue);
+ assertEquals("Image Path did not change as expected", mappedImagePath, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ @Test
+ public void testResourceField() throws Exception {
+ final String imagePath = "/content/test/image/test-image.jpg";
+ final String mappedImagePath = "/image/test-image.jpg";
+ final String name = "imagePath";
+
+ Whitebox.setInternalState(
+ externalizePathSerializer, "externalizePathProviderManager", externalizePathProviderManagerService
+ );
+ AnnotationResourceFieldModel model = new AnnotationResourceFieldModel(resource, imagePath);
+ when(resourceResolver.map(eq(imagePath))).thenReturn(mappedImagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertNotEquals("Image Path should have changed", imagePath, stringValue);
+ assertEquals("Image Path did not change as expected", mappedImagePath, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ @Test
+ public void testAnnotationOnField() throws Exception {
+ final String imagePath = "/content/test/image/test-image.jpg";
+ final String mappedImagePath = "/image/test-image.jpg";
+ final String name = "imagePath";
+
+ Whitebox.setInternalState(
+ externalizePathSerializer, "externalizePathProviderManager", externalizePathProviderManagerService
+ );
+ FieldAnnotatedModel model = new FieldAnnotatedModel(resource, imagePath);
+ when(resourceResolver.map(eq(imagePath))).thenReturn(mappedImagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertNotEquals("Image Path should have changed", imagePath, stringValue);
+ assertEquals("Image Path did not change as expected", mappedImagePath, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ @Test
+ public void testAnnotationOnSubclassField() throws Exception {
+ final String imagePath = "/content/test/image/test-image.jpg";
+ final String mappedImagePath = "/image/test-image.jpg";
+ final String name = "imagePath";
+
+ Whitebox.setInternalState(
+ externalizePathSerializer, "externalizePathProviderManager", externalizePathProviderManagerService
+ );
+ SubclassFieldAnnotatedModel model = new SubclassFieldAnnotatedModel(resource, imagePath);
+ when(resourceResolver.map(eq(imagePath))).thenReturn(mappedImagePath);
+ doAnswer(
+ invocation -> {
+ String fieldName = (String) invocation.getArguments()[1];
+ Object value = invocation.getArguments()[2];
+ if(fieldName.equals(name)) {
+ String stringValue = (String) value;
+ assertNotEquals("Image Path should have changed", imagePath, stringValue);
+ assertEquals("Image Path did not change as expected", mappedImagePath, stringValue);
+ }
+ return null;
+ }
+ ).when(externalizePathSerializer).createProperty(any(JsonGenerator.class), anyString(), any(Object.class), any(SerializerProvider.class));
+ externalizePathSerializer.serialize(model, jsonGenerator, serializerProvider);
+ }
+
+ private abstract static class AbstractModel {
+ private Resource resource;
+ private String path;
+
+ public AbstractModel(Resource resource, String path) {
+ this.resource = resource;
+ this.path = path;
+ }
+
+ public Resource getResource() { return resource; }
+
+ public String getImagePath() {
+ return path;
+ }
+ }
+
+ private static class NoAnnotationModel extends AbstractModel {
+ public NoAnnotationModel(Resource resource, String path) {
+ super(resource, path);
+ }
+
+ public String getImagePath() {
+ return super.getImagePath();
+ }
+ }
+
+ private static class MethodAnnotatedModel extends AbstractModel {
+ public MethodAnnotatedModel(Resource resource, String path) {
+ super(resource, path);
+ }
+
+ @ExternalizePath
+ public String getImagePath() {
+ return super.getImagePath();
+ }
+ }
+
+ private static class MethodAnnotatedInInheritanceModel
+ extends MethodAnnotatedModel
+ {
+ @ExternalizePath
+ private String testPath;
+
+ public MethodAnnotatedInInheritanceModel(Resource resource, String path, String testPath) {
+ super(resource, path);
+ this.testPath = testPath;
+ }
+
+ public String getTestPath() {
+ return testPath;
+ }
+ }
+
+ private static class AnnotationResourceMethodModel {
+ private Resource myResource;
+ private String path;
+ public AnnotationResourceMethodModel(Resource resource, String path) {
+ this.myResource = resource;
+ this.path = path;
+ }
+
+ @ExternalizePath(resourceMethod = "getMyResource")
+ public String getImagePath() {
+ return path;
+ }
+
+ public Resource getMyResource() {
+ return myResource;
+ }
+ }
+
+ private static class AnnotationResourceFieldModel {
+ private Resource anotherResource;
+ private String path;
+ public AnnotationResourceFieldModel(Resource resource, String path) {
+ this.anotherResource = resource;
+ this.path = path;
+ }
+
+ @ExternalizePath(resourceField = "anotherResource")
+ public String getImagePath() {
+ return path;
+ }
+
+ public Resource getMyResource() {
+ return anotherResource;
+ }
+ }
+
+ private static class FieldAnnotatedModel {
+ private Resource resource;
+ @ExternalizePath
+ private String imagePath;
+
+ public FieldAnnotatedModel(Resource resource, String path) {
+ this.resource = resource;
+ this.imagePath = path;
+ }
+
+ public String getImagePath() {
+ return imagePath;
+ }
+ }
+
+ private static class SubclassFieldAnnotatedModel extends FieldAnnotatedModel {
+ public SubclassFieldAnnotatedModel(Resource resource, String path) {
+ super(resource, path);
+ }
+ }
+
+ private static class TestExternalizePathProvider
+ implements ExternalizePathProvider
+ {
+ private String from;
+ private String to;
+
+ public TestExternalizePathProvider(String from, String to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ public String externalize(@NotNull Object model, ExternalizePath annocation, String sourcePath) {
+ String answer = sourcePath;
+ if(sourcePath.startsWith(from)) {
+ answer = to + sourcePath.substring(from.length());
+ }
+ return answer;
+ }
+ }
+}