/* * Copyright 2009 Google Inc. * * 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 com.google.gwt.rpc.server; import com.google.gwt.rpc.client.ast.ArrayValueCommand; import com.google.gwt.rpc.client.ast.CommandSink; import com.google.gwt.rpc.client.ast.EnumValueCommand; import com.google.gwt.rpc.client.ast.HasSetters; import com.google.gwt.rpc.client.ast.IdentityValueCommand; import com.google.gwt.rpc.client.ast.InstantiateCommand; import com.google.gwt.rpc.client.ast.InvokeCustomFieldSerializerCommand; import com.google.gwt.rpc.client.ast.NullValueCommand; import com.google.gwt.rpc.client.ast.ValueCommand; import com.google.gwt.rpc.client.impl.CommandSerializationStreamWriterBase; import com.google.gwt.rpc.client.impl.HasValuesCommandSink; import com.google.gwt.rpc.server.CommandSerializationUtil.Accessor; import com.google.gwt.user.client.rpc.IsSerializable; import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.server.rpc.impl.SerializabilityUtil; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.IdentityHashMap; import java.util.Map; /** * A server-side implementation of SerializationStreamWriter that creates a * command stream. */ public class CommandServerSerializationStreamWriter extends CommandSerializationStreamWriterBase { private final ClientOracle clientOracle; private final Map<Object, IdentityValueCommand> identityMap; public CommandServerSerializationStreamWriter(CommandSink sink) { this(new HostedModeClientOracle(), sink); } public CommandServerSerializationStreamWriter(ClientOracle oracle, CommandSink sink) { this(oracle, sink, new IdentityHashMap<Object, IdentityValueCommand>()); } private CommandServerSerializationStreamWriter(ClientOracle oracle, CommandSink sink, Map<Object, IdentityValueCommand> identityMap) { super(sink); this.clientOracle = oracle; this.identityMap = identityMap; } /** * Type is passed in to handle primitive types. */ @Override protected ValueCommand makeValue(Class<?> type, Object value) throws SerializationException { if (value == null) { return NullValueCommand.INSTANCE; } /* * Check accessor map before the identity map because we don't want to * recurse on wrapped primitive values. */ Accessor accessor; if ((accessor = CommandSerializationUtil.getAccessor(type)).canMakeValueCommand()) { return accessor.makeValueCommand(value); } else if (identityMap.containsKey(value)) { return identityMap.get(value); } else if (type.isArray()) { return makeArray(type, value); } else if (Enum.class.isAssignableFrom(type)) { return makeEnum(value); } else { return makeObject(type, value); } } private ArrayValueCommand makeArray(Class<?> type, Object value) throws SerializationException { ArrayValueCommand toReturn = new ArrayValueCommand(type.getComponentType()); identityMap.put(value, toReturn); for (int i = 0, j = Array.getLength(value); i < j; i++) { Object arrayValue = Array.get(value, i); if (arrayValue == null) { toReturn.add(NullValueCommand.INSTANCE); } else { Class<? extends Object> valueType = type.getComponentType().isPrimitive() ? type.getComponentType() : arrayValue.getClass(); toReturn.add(makeValue(valueType, arrayValue)); } } return toReturn; } private ValueCommand makeEnum(Object value) { EnumValueCommand toReturn = new EnumValueCommand(); toReturn.setValue((Enum<?>) value); return toReturn; } /* * TODO: Profiling shows that the reflection and conditional logic in this * method is a hotspot. This could be remedied by generating synthetic * InstantiateCommand types that initialize themselves. */ private IdentityValueCommand makeObject(Class<?> type, Object value) throws SerializationException { if (type.isAnonymousClass() || type.isLocalClass()) { throw new SerializationException( "Cannot serialize anonymous or local classes"); } Class<?> manualType = type; Class<?> customSerializer; do { customSerializer = SerializabilityUtil.hasCustomFieldSerializer(manualType); if (customSerializer != null) { break; } manualType = manualType.getSuperclass(); } while (manualType != null); IdentityValueCommand ins; if (customSerializer != null) { ins = serializeWithCustomSerializer(customSerializer, value, type, manualType); } else { ins = new InstantiateCommand(type); identityMap.put(value, ins); } /* * If we're looking at a subclass of a manually-serialized type, the * subclass must be tagged as serializable in order to qualify for * serialization. */ if (type != manualType) { if (!Serializable.class.isAssignableFrom(type) && !IsSerializable.class.isAssignableFrom(type)) { throw new SerializationException(type.getName() + " is not a serializable type"); } } while (type != manualType) { Field[] serializableFields = clientOracle.getOperableFields(type); for (Field declField : serializableFields) { assert (declField != null); Accessor accessor = CommandSerializationUtil.getAccessor(declField.getType()); ValueCommand valueCommand; Object fieldValue = accessor.get(value, declField); if (fieldValue == null) { valueCommand = NullValueCommand.INSTANCE; } else { Class<? extends Object> fieldType = declField.getType().isPrimitive() ? declField.getType() : fieldValue.getClass(); valueCommand = makeValue(fieldType, fieldValue); } ((HasSetters) ins).set(declField.getDeclaringClass(), declField.getName(), valueCommand); } type = type.getSuperclass(); } return ins; } private InvokeCustomFieldSerializerCommand serializeWithCustomSerializer( Class<?> customSerializer, Object instance, Class<?> instanceClass, Class<?> manuallySerializedType) throws SerializationException { assert !instanceClass.isArray(); Exception ex; try { /* * NB: Class.getMethod() wants exact formal types. It may be the case that * the custom serializer uses looser type bounds in its method * declarations. */ for (Method method : customSerializer.getMethods()) { if ("serialize".equals(method.getName())) { assert Modifier.isStatic(method.getModifiers()) : "serialize method " + "in type " + customSerializer.getName() + " must be static"; final InvokeCustomFieldSerializerCommand toReturn = new InvokeCustomFieldSerializerCommand( instanceClass, customSerializer, manuallySerializedType); identityMap.put(instance, toReturn); /* * Pass the current identityMap into the new writer to allow circular * references through the graph emitted by the CFS. */ CommandServerSerializationStreamWriter subWriter = new CommandServerSerializationStreamWriter( clientOracle, new HasValuesCommandSink(toReturn), identityMap); method.invoke(null, subWriter, instance); return toReturn; } } throw new NoSuchMethodException( "Could not find serialize method in custom serializer " + customSerializer.getName()); } catch (SecurityException e) { ex = e; } catch (NoSuchMethodException e) { ex = e; } catch (IllegalArgumentException e) { ex = e; } catch (IllegalAccessException e) { ex = e; } catch (InvocationTargetException e) { ex = e; } throw new SerializationException(ex); } }