blob: 76ffd99752b98ff92c7564082ac23f3e6c4a9528 [file] [log] [blame]
/*
* 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<length; i++) {
values[i] = Array.get(value, i);
}
}
if (!value.getClass().isArray()) {
jgen.writeFieldName(name);
provider.defaultSerializeValue(value, jgen);
} else {
jgen.writeArrayFieldStart(name);
for (Object v : values) {
provider.defaultSerializeValue(v, jgen);
}
jgen.writeEndArray();
}
}
/** @return Obtains the Externalize Path Provider Manager to obtain the highest Provider which is then returned **/
private ExternalizePathProvider getExternalizedPathProvider() {
ExternalizePathProvider answer = null;
ExternalizePathProviderManager manager =
this.externalizePathProviderManager != null ?
this.externalizePathProviderManager :
getService(ExternalizePathProviderManager.class, ExternalizePathProviderManager.class);
if(manager != null) {
answer = manager.getExternalizedPathProvider();
} else {
logger.warn("Externalized Path Provider Manager not found");
}
return answer;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> T getService(Class clazz, Class<T> type) {
Bundle currentBundle = FrameworkUtil.getBundle(clazz);
if (currentBundle == null) {
return null;
}
BundleContext bundleContext = currentBundle.getBundleContext();
if (bundleContext == null) {
return null;
}
ServiceReference<T> serviceReference = bundleContext.getServiceReference(type);
if (serviceReference == null) {
return null;
}
T service = bundleContext.getService(serviceReference);
if (service == null) {
return null;
}
return service;
}
}