/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI 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.openengsb.core.util;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.List;
import org.apache.commons.lang.reflect.MethodUtils;
import org.openengsb.core.api.model.OpenEngSBModelEntry;
import org.openengsb.core.api.model.annotation.Model;
import org.openengsb.core.api.remote.MethodCall;
import org.openengsb.core.api.remote.MethodCallMessage;
import org.openengsb.core.api.remote.MethodResult;
import org.openengsb.core.api.remote.MethodResultMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import com.google.common.base.Preconditions;
public final class JsonUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final ObjectMapper MODEL_MAPPER = new ObjectMapper();
static {
// adding the additional deserializer needed to deserialize models
MODEL_MAPPER.registerModule(new SimpleModule().addDeserializer(Object.class,
new OpenEngSBModelEntryDeserializer()));
}
/**
* Converts an object in JSON format to the given class. Throws an IOException if the conversion could not be
* performed.
*/
public static <T> T convertObject(String json, Class<T> clazz) throws IOException {
try {
if (clazz.isAnnotationPresent(Model.class)) {
return MODEL_MAPPER.readValue(json, clazz);
}
return MAPPER.readValue(json, clazz);
} catch (IOException e) {
String error = String.format("Unable to parse given json '%s' into class '%s'.", json, clazz.getName());
LOGGER.error(error, e);
throw new IOException(error, e);
}
}
private static Object convertArgument(String className, Object arg) {
try {
Class<?> type = findType(className);
if (type.isAnnotationPresent(Model.class)) {
return MODEL_MAPPER.convertValue(arg, type);
}
return MAPPER.convertValue(arg, type);
} catch (ClassNotFoundException e) {
LOGGER.error("could not convert argument " + arg, e);
return arg;
}
}
private static Class<?> findType(String className) throws ClassNotFoundException {
if (className.startsWith("[L")) {
Class<?> componentType = findType(className.substring(2, className.length() - 1));
return Array.newInstance(componentType, 0).getClass();
}
return JsonUtils.class.getClassLoader().loadClass(className);
}
public static void convertAllArgs(MethodCall call) {
Object[] args = call.getArgs();
List<String> classes = call.getClasses();
Preconditions.checkArgument(args.length == classes.size());
for (int i = 0; i < args.length; i++) {
args[i] = convertArgument(classes.get(i), args[i]);
}
}
public static void convertResult(MethodResult result) {
Object convertArgument = convertArgument(result.getClassName(), result.getArg());
result.setArg(convertArgument);
}
public static void convertAllArgs(MethodCallMessage request) {
convertAllArgs(request.getMethodCall());
}
public static void convertResult(MethodResultMessage message) {
convertResult(message.getResult());
}
public static ObjectMapper createObjectMapperWithIntroSpectors() {
ObjectMapper mapper = new ObjectMapper();
AnnotationIntrospector primaryIntrospector = new JacksonAnnotationIntrospector();
AnnotationIntrospector secondaryIntrospector = new JaxbAnnotationIntrospector(mapper.getTypeFactory());
AnnotationIntrospector introspector =
new AnnotationIntrospectorPair(primaryIntrospector, secondaryIntrospector);
mapper.getDeserializationConfig().withAppendedAnnotationIntrospector(introspector);
mapper.getSerializationConfig().withAppendedAnnotationIntrospector(introspector);
return mapper;
}
private JsonUtils() {
}
/**
* The OpenEngSBModelEntryDeserializer class is needed in order to be able to transform the list of
* OpenEngSBModelEntry elements, which is contained in every model tail, from a JSON string into a list of actual
* elements.
*/
@SuppressWarnings("serial")
private static class OpenEngSBModelEntryDeserializer extends StdScalarDeserializer<OpenEngSBModelEntry> {
public OpenEngSBModelEntryDeserializer() {
super(OpenEngSBModelEntry.class);
}
@Override
public OpenEngSBModelEntry deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonToken token = jp.getCurrentToken();
OpenEngSBModelEntry entry = new OpenEngSBModelEntry();
if (token != JsonToken.START_OBJECT) {
return null;
} else {
// skip the JsonToken.START_OBJECT token
token = jp.nextValue();
}
do {
if (token == JsonToken.END_OBJECT) {
Object value = createValueOfEntry(entry);
if (value != null) {
entry.setValue(value);
}
return entry;
} else {
if (jp.getCurrentName().equals("key")) {
entry.setKey(jp.getValueAsString());
} else if (jp.getCurrentName().equals("value")) {
entry.setValue(jp.getValueAsString());
} else if (jp.getCurrentName().equals("type")) {
try {
entry.setType(findType(jp.getValueAsString()));
} catch (ClassNotFoundException e) {
LOGGER.error("Did not find class of type " + jp.getValueAsString(), e);
break;
}
}
}
token = jp.nextValue();
} while (token != null);
return null;
}
/**
* Converts the string located in the value property of the entry in the correct data format and returns it.
*/
private Object createValueOfEntry(OpenEngSBModelEntry entry) {
if (entry.getType().equals(String.class)) {
return entry.getValue();
}
Object element = null;
if (entry.getType() == null) {
LOGGER.error("Unknown type for model entry with key {}", entry.getKey());
return element;
}
try {
Class<?> clazz = entry.getType();
Constructor<?> constr = ClassUtils.getConstructorIfAvailable(clazz, String.class);
if (constr != null) {
element = constr.newInstance(entry.getValue());
} else {
element = MethodUtils.invokeStaticMethod(clazz, "valueOf", entry.getValue());
}
} catch (Exception e) {
LOGGER.error("Unable to convert value with the key {} to correct type", entry.getKey());
}
return element;
}
}
}