/*
* Copyright (C) 2011 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.jboss.errai.marshalling.server.marshallers;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.common.client.protocols.SerializationParts;
import org.jboss.errai.marshalling.client.api.Marshaller;
import org.jboss.errai.marshalling.client.api.MarshallingSession;
import org.jboss.errai.marshalling.client.api.exceptions.MarshallingException;
import org.jboss.errai.marshalling.client.api.json.EJObject;
import org.jboss.errai.marshalling.client.api.json.EJValue;
import org.jboss.errai.marshalling.client.util.MarshallUtil;
import org.jboss.errai.marshalling.client.util.NumbersUtils;
import org.jboss.errai.marshalling.rebind.DefinitionsFactory;
import org.jboss.errai.marshalling.rebind.api.model.ConstructorMapping;
import org.jboss.errai.marshalling.rebind.api.model.FactoryMapping;
import org.jboss.errai.marshalling.rebind.api.model.InstantiationMapping;
import org.jboss.errai.marshalling.rebind.api.model.Mapping;
import org.jboss.errai.marshalling.rebind.api.model.MappingDefinition;
import org.jboss.errai.marshalling.rebind.api.model.MemberMapping;
import org.jboss.errai.marshalling.server.EncodingSession;
import org.jboss.errai.marshalling.server.MappingContextSingleton;
import org.jboss.errai.marshalling.server.api.ServerMarshaller;
import org.mvel2.DataConversion;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
/**
* @author Mike Brock
*/
public class DefaultDefinitionMarshaller implements ServerMarshaller<Object> {
static final Charset UTF_8 = Charset.forName("UTF-8");
private final MappingDefinition definition;
public DefaultDefinitionMarshaller(final MappingDefinition definition) {
this.definition = definition;
}
public static void setProperty(final Object i, final Field f, final Object v) {
try {
f.setAccessible(true);
f.set(i, DataConversion.convert(v, f.getType()));
}
catch (Exception e) {
throw new RuntimeException("could not set field (inst=" + i + "; field=" + f + "; val=" + v + ")", e);
}
}
@SuppressWarnings("unchecked")
//@Override
private Class<Object> getTypeHandled() {
return (Class<Object>) definition.getMappingClass().asClass();
}
@Override
public Object[] getEmptyArray() {
return (Object[]) Array.newInstance(getTypeHandled(), 0);
}
@SuppressWarnings({"ConstantConditions", "unchecked"})
@Override
public Object demarshall(final EJValue o, final MarshallingSession ctx) {
try {
if (o.isObject() != null) {
final EJObject oMap = o.isObject();
final Object newInstance;
if (MarshallUtil.isEncodedObject(oMap)) {
if (MarshallUtil.isEncodedNumeric(oMap)) {
return NumbersUtils.getEncodedNumber(oMap);
}
final String objID = oMap.get(SerializationParts.OBJECT_ID).isString().stringValue();
if (ctx.hasObject(objID)) {
newInstance = ctx.getObject(Object.class, objID);
/**
* If this only contains 2 fields, it is only a graph reference.
*/
if (oMap.size() == 2) {
return newInstance;
}
}
else {
/**
* Check to see if this object is instantiate only... meaning it has no fields to marshall.
*/
if (oMap.containsKey(SerializationParts.INSTANTIATE_ONLY)) {
newInstance = getTypeHandled().newInstance();
ctx.recordObject(objID, newInstance);
return newInstance;
}
final InstantiationMapping cMapping = definition.getInstantiationMapping();
final Object[] parms = new Object[cMapping.getMappings().length];
final Class[] targetTypes = cMapping.getSignature();
int i = 0;
for (final Mapping mapping : cMapping.getMappings()) {
final Marshaller<Object> marshaller = ctx.getMarshallerInstance(mapping.getType().getFullyQualifiedName());
//noinspection unchecked
parms[i] = DataConversion.convert(
marshaller.demarshall(oMap.get(mapping.getKey()), ctx), targetTypes[i++]);
}
if (cMapping instanceof ConstructorMapping) {
final Constructor constructor = ((ConstructorMapping) cMapping).getMember().asConstructor();
constructor.setAccessible(true);
newInstance = constructor.newInstance(parms);
}
else {
newInstance = ((FactoryMapping) cMapping).getMember().asMethod().invoke(null, parms);
}
ctx.recordObject(objID, newInstance);
}
for (final MemberMapping mapping : definition.getWritableMemberMappings()) {
final EJValue o1 = oMap.get(mapping.getKey());
if (!o1.isNull()) {
final Marshaller<Object> marshaller
= ctx.getMarshallerInstance(mapping.getType().getFullyQualifiedName());
if (mapping.getBindingMember() instanceof MetaField) {
final MetaField f = (MetaField) mapping.getBindingMember();
setProperty(newInstance, f.asField(),
marshaller.demarshall(o1, ctx));
}
else {
final Method m = ((MetaMethod) mapping.getBindingMember()).asMethod();
m.invoke(newInstance, DataConversion.convert(
marshaller.demarshall(o1, ctx),
m.getParameterTypes()[0]));
}
}
}
return newInstance;
}
else if (oMap.containsKey(SerializationParts.ENUM_STRING_VALUE)) {
return Enum.valueOf(getClassReference(oMap),
oMap.get(SerializationParts.ENUM_STRING_VALUE).isString().stringValue());
}
else {
throw new RuntimeException("bad payload");
}
}
else {
return o.getRawValue();
}
}
catch (Exception e) {
throw new MarshallingException("Failed to demarshall an instance of " + definition.getMappingClass(), e);
}
}
@Override
public String marshall(final Object o, final MarshallingSession ctx) {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
try {
marshall(byteArrayOutputStream, o, ctx);
return new String(byteArrayOutputStream.toByteArray(), UTF_8);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void marshall(final OutputStream outstream, final Object o, final MarshallingSession mSession) throws IOException {
if (o == null) {
outstream.write("null".getBytes(UTF_8));
return;
}
final EncodingSession ctx = (EncodingSession) mSession;
final Class cls = o.getClass();
if (definition.getMappingClass().isEnum()) {
final Enum enumer = (Enum) o;
outstream.write(("{\"" + SerializationParts.ENCODED_TYPE + "\":\""
+ enumer.getDeclaringClass().getName() + "\""
+ ",\"" + SerializationParts.ENUM_STRING_VALUE + "\":\"" + enumer.name() + "\"}")
.getBytes(UTF_8));
return;
}
final boolean enc = ctx.hasObject(o);
final String hash = ctx.getObject(o);
if (enc) {
/**
* If this object is referencing a duplicate object in the graph, we only provide an ID reference.
*/
outstream.write(("{\"" + SerializationParts.ENCODED_TYPE + "\":\"" + cls.getName()
+ "\",\"" + SerializationParts.OBJECT_ID + "\":\"" + hash + "\"}").getBytes(UTF_8));
return;
}
int i = 0;
boolean first = true;
outstream.write(("{\"" + SerializationParts.ENCODED_TYPE + "\":\"" + cls.getName() + "\",\""
+ SerializationParts.OBJECT_ID + "\":\"" + hash + "\",").getBytes(UTF_8));
for (final MemberMapping mapping : definition.getReadableMemberMappings()) {
if (!first) {
outstream.write(',');
}
i++;
final Object v;
if (mapping.getReadingMember() instanceof MetaField) {
final Field field = ((MetaField) mapping.getReadingMember()).asField();
field.setAccessible(true);
try {
v = field.get(o);
}
catch (Exception e) {
throw new RuntimeException("error accessing field: " + field, e);
}
}
else {
final Method method = ((MetaMethod) mapping.getReadingMember()).asMethod();
method.setAccessible(true);
try {
v = method.invoke(o);
}
catch (Exception e) {
throw new RuntimeException("error calling getter: " + method, e);
}
}
outstream.write(("\"" + mapping.getKey() + "\"").getBytes(UTF_8));
outstream.write(':');
if (v == null) {
outstream.write("null".getBytes(UTF_8));
}
else {
final DefinitionsFactory definitionsFactory = MappingContextSingleton.get().getDefinitionsFactory();
if (definitionsFactory == null) {
throw new RuntimeException("definition factory is null!");
}
final MappingDefinition definition1 = definitionsFactory.getDefinition(mapping.getType());
if (definition1 == null) {
throw new RuntimeException("no mapping definition for: " + mapping.getType().getFullyQualifiedName());
}
final Marshaller<Object> marshallerInstance = definition1.getMarshallerInstance();
if (marshallerInstance == null) {
throw new RuntimeException("no marshaller instance for: " + mapping.getType().getFullyQualifiedName());
}
outstream.write(
marshallerInstance.marshall(v, ctx)
.getBytes(UTF_8)
);
}
first = false;
}
if (i == 0) {
outstream.write(("\"" + SerializationParts.INSTANTIATE_ONLY + "\":true").getBytes(UTF_8));
}
outstream.write('}');
}
public static Class getClassReference(final EJObject oMap) {
try {
return Thread.currentThread().getContextClassLoader()
.loadClass(oMap.get(SerializationParts.ENCODED_TYPE).isString().stringValue());
}
catch (ClassNotFoundException e) {
throw new RuntimeException("could not instantiate class", e);
}
}
}