blob: 46cf9bed8b28331bc248a989afbcbf47e35f5ca5 [file] [log] [blame]
/*
* Copyright 2008 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.user.server.rpc.impl;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.GwtTransient;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.zip.CRC32;
/**
* Serialization utility class used by the server-side RPC code.
*/
public class SerializabilityUtil {
public static final String DEFAULT_ENCODING = "UTF-8";
/**
* Comparator used to sort fields.
*/
public static final Comparator<Field> FIELD_COMPARATOR = new Comparator<Field>() {
public int compare(Field f1, Field f2) {
return f1.getName().compareTo(f2.getName());
}
};
/**
* A permanent cache of all computed CRCs on classes. This is safe to do
* because a Class is guaranteed not to change within the lifetime of a
* ClassLoader (and thus, this Map). Access must be synchronized.
*
* NOTE: after synchronizing on this field, it's possible to additionally
* synchronize on {@link #classCustomSerializerCache} or
* {@link #classSerializableFieldsCache}, so be aware deadlock potential when
* changing this code.
*/
private static final Map<Class<?>, String> classCRC32Cache = new IdentityHashMap<Class<?>, String>();
/**
* A permanent cache of all serializable fields on classes. This is safe to do
* because a Class is guaranteed not to change within the lifetime of a
* ClassLoader (and thus, this Map). Access must be synchronized.
*
* NOTE: to prevent deadlock, you may NOT synchronize {@link #classCRC32Cache}
* after synchronizing on this field.
*/
private static final Map<Class<?>, Field[]> classSerializableFieldsCache = new IdentityHashMap<Class<?>, Field[]>();
/**
* A permanent cache of all which classes onto custom field serializers. This
* is safe to do because a Class is guaranteed not to change within the
* lifetime of a ClassLoader (and thus, this Map). Access must be
* synchronized.
*
* NOTE: to prevent deadlock, you may NOT synchronize {@link #classCRC32Cache}
* after synchronizing on this field.
*/
private static final Map<Class<?>, Class<?>> classCustomSerializerCache = new IdentityHashMap<Class<?>, Class<?>>();
/**
* Map of {@link Class} objects to singleton instances of that
* {@link CustomFieldSerializer}.
*/
private static final Map<Class<?>, CustomFieldSerializer<?>>
CLASS_TO_SERIALIZER_INSTANCE =
new IdentityHashMap<Class<?>, CustomFieldSerializer<?>>();
private static final String JRE_SERIALIZER_PACKAGE = "com.google.gwt.user.client.rpc.core";
/**
* A re-usable, non-functional {@link CustomFieldSerializer} for when the
* Custom Field Serializer does not implement the
* {@link CustomFieldSerializer} interface.
*/
private static final CustomFieldSerializer<?> NO_SUCH_SERIALIZER =
new CustomFieldSerializer<Object>() {
@Override
public void deserializeInstance(SerializationStreamReader
streamReader, Object instance) {
throw new AssertionError("This should never be called.");
}
@Override
public void serializeInstance(SerializationStreamWriter streamWriter,
Object instance) {
throw new AssertionError("This should never be called.");
}
};
private static final Map<String, String> SERIALIZED_PRIMITIVE_TYPE_NAMES = new HashMap<String, String>();
private static final Set<Class<?>> TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES = new HashSet<Class<?>>();
static {
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(boolean.class.getName(), "Z");
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(byte.class.getName(), "B");
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(char.class.getName(), "C");
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(double.class.getName(), "D");
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(float.class.getName(), "F");
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(int.class.getName(), "I");
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(long.class.getName(), "J");
SERIALIZED_PRIMITIVE_TYPE_NAMES.put(short.class.getName(), "S");
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Boolean.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Byte.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Character.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Double.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Exception.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Float.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Integer.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Long.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Object.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Short.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(String.class);
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Throwable.class);
try {
/*
* Work around for incompatible type hierarchy (and therefore signature)
* between JUnit3 and JUnit4. Do this via reflection so we don't force the
* server to depend on JUnit.
*/
Class<?> clazz = Class.forName("junit.framework.AssertionFailedError");
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(clazz);
} catch (ClassNotFoundException dontCare) {
}
}
/**
* Returns the fields of a particular class that can be considered for
* serialization. The returned list will be sorted into a canonical order to
* ensure consistent answers.
*
* TODO: this method needs a better name, I think.
*/
public static Field[] applyFieldSerializationPolicy(Class<?> clazz) {
Field[] serializableFields;
synchronized (classSerializableFieldsCache) {
serializableFields = classSerializableFieldsCache.get(clazz);
if (serializableFields == null) {
ArrayList<Field> fieldList = new ArrayList<Field>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (fieldQualifiesForSerialization(field)) {
fieldList.add(field);
}
}
serializableFields = fieldList.toArray(new Field[fieldList.size()]);
// sort the fields by name
Arrays.sort(serializableFields, 0, serializableFields.length,
FIELD_COMPARATOR);
classSerializableFieldsCache.put(clazz, serializableFields);
}
}
return serializableFields;
}
public static SerializedInstanceReference decodeSerializedInstanceReference(
String encodedSerializedInstanceReference) {
final String[] components = encodedSerializedInstanceReference.split(SerializedInstanceReference.SERIALIZED_REFERENCE_SEPARATOR);
return new SerializedInstanceReference() {
public String getName() {
return components.length > 0 ? components[0] : "";
}
public String getSignature() {
return components.length > 1 ? components[1] : "";
}
};
}
public static String encodeSerializedInstanceReference(Class<?> instanceType, SerializationPolicy policy) {
return instanceType.getName()
+ SerializedInstanceReference.SERIALIZED_REFERENCE_SEPARATOR
+ getSerializationSignature(instanceType, policy);
}
public static String getSerializationSignature(Class<?> instanceType,
SerializationPolicy policy) {
String result;
synchronized (classCRC32Cache) {
result = classCRC32Cache.get(instanceType);
if (result == null) {
CRC32 crc = new CRC32();
try {
generateSerializationSignature(instanceType, crc, policy);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(
"Could not compute the serialization signature", e);
}
result = Long.toString(crc.getValue());
classCRC32Cache.put(instanceType, result);
}
}
return result;
}
public static String getSerializedTypeName(Class<?> instanceType) {
if (instanceType.isPrimitive()) {
return SERIALIZED_PRIMITIVE_TYPE_NAMES.get(instanceType.getName());
}
return instanceType.getName();
}
/**
* Returns the {@link Class} which can serialize the given instance type, or
* <code>null</code> if this class has no custom field serializer. Note that
* arrays never have custom field serializers.
*/
public static Class<?> hasCustomFieldSerializer(Class<?> instanceType) {
assert (instanceType != null);
if (instanceType.isArray()) {
return null;
}
Class<?> result;
synchronized (classCustomSerializerCache) {
result = classCustomSerializerCache.get(instanceType);
if (result == null) {
result = computeHasCustomFieldSerializer(instanceType);
if (result == null) {
/*
* Use (result == instanceType) as a sentinel value when the class has
* no custom field serializer. We avoid using null as the sentinel
* value, because that would necessitate an additional containsKey()
* check in the most common case, inside the synchronized block.
*/
result = instanceType;
}
classCustomSerializerCache.put(instanceType, result);
}
}
return (result == instanceType) ? null : result;
}
static boolean isNotStaticTransientOrFinal(Field field) {
/*
* Only serialize fields that are not static, transient (including @GwtTransient), or final.
*/
int fieldModifiers = field.getModifiers();
return !Modifier.isStatic(fieldModifiers)
&& !Modifier.isTransient(fieldModifiers)
&& !field.isAnnotationPresent(GwtTransient.class)
&& !Modifier.isFinal(fieldModifiers);
}
/**
* Loads a {@link CustomFieldSerializer} from a class that may implement that
* interface.
*
* @param customSerializerClass the Custom Field Serializer class
*
* @return an instance the class provided if it implements
* {@link CustomFieldSerializer} or {@code null} if it does not
*
* @throws SerializationException if the load process encounters an
* unexpected problem
*/
static CustomFieldSerializer<?> loadCustomFieldSerializer(
final Class<?> customSerializerClass) throws SerializationException {
/**
* Note that neither reading or writing to the CLASS_TO_SERIALIZER_INSTANCE
* is synchronized for performance reasons. This could cause get misses,
* put misses and the same CustomFieldSerializer to be instantiated more
* than once, but none of these are critical operations as
* CLASS_TO_SERIALIZER_INSTANCE is only a performance improving cache.
*/
CustomFieldSerializer<?> customFieldSerializer =
CLASS_TO_SERIALIZER_INSTANCE.get(customSerializerClass);
if (customFieldSerializer == null) {
if (CustomFieldSerializer.class.isAssignableFrom(customSerializerClass)) {
try {
customFieldSerializer =
(CustomFieldSerializer<?>) customSerializerClass.newInstance();
} catch (InstantiationException e) {
throw new SerializationException(e);
} catch (IllegalAccessException e) {
throw new SerializationException(e);
}
} else {
customFieldSerializer = NO_SUCH_SERIALIZER;
}
CLASS_TO_SERIALIZER_INSTANCE.put(customSerializerClass,
customFieldSerializer);
}
if (customFieldSerializer == NO_SUCH_SERIALIZER) {
return null;
} else {
return customFieldSerializer;
}
}
/**
* This method treats arrays in a special way.
*/
private static Class<?> computeHasCustomFieldSerializer(Class<?> instanceType) {
assert (instanceType != null);
String qualifiedTypeName = instanceType.getName();
/*
* This class is called from client code running in Development Mode as well
* as server code running in the servlet container. In Development Mode, we
* want to load classes through the
* CompilingClassLoader$MultiParentClassLoader, not the system classloader.
*/
ClassLoader classLoader = GWT.isClient()
? SerializabilityUtil.class.getClassLoader()
: Thread.currentThread().getContextClassLoader();
String simpleSerializerName = qualifiedTypeName + "_CustomFieldSerializer";
Class<?> customSerializer = getCustomFieldSerializer(classLoader,
simpleSerializerName);
if (customSerializer != null) {
return customSerializer;
}
// Try with the regular name
Class<?> customSerializerClass = getCustomFieldSerializer(classLoader,
JRE_SERIALIZER_PACKAGE + "." + simpleSerializerName);
if (customSerializerClass != null) {
return customSerializerClass;
}
return null;
}
private static boolean excludeImplementationFromSerializationSignature(
Class<?> instanceType) {
if (TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.contains(instanceType)) {
return true;
}
return false;
}
private static boolean fieldQualifiesForSerialization(Field field) {
if (Throwable.class == field.getDeclaringClass()) {
/**
* Only serialize Throwable's detailMessage field; all others are ignored.
*
* NOTE: Changing the set of fields that we serialize for Throwable will
* necessitate a change to our JRE emulation's version of Throwable.
*/
if ("detailMessage".equals(field.getName())) {
assert (isNotStaticTransientOrFinal(field));
return true;
} else {
return false;
}
} else {
return isNotStaticTransientOrFinal(field);
}
}
private static void generateSerializationSignature(Class<?> instanceType,
CRC32 crc, SerializationPolicy policy) throws UnsupportedEncodingException {
crc.update(getSerializedTypeName(instanceType).getBytes(DEFAULT_ENCODING));
if (excludeImplementationFromSerializationSignature(instanceType)) {
return;
}
Class<?> customSerializer = hasCustomFieldSerializer(instanceType);
if (customSerializer != null) {
generateSerializationSignature(customSerializer, crc, policy);
} else if (instanceType.isArray()) {
generateSerializationSignature(instanceType.getComponentType(), crc, policy);
} else if (!instanceType.isPrimitive()) {
Field[] fields = applyFieldSerializationPolicy(instanceType);
Set<String> clientFieldNames = policy.getClientFieldNamesForEnhancedClass(instanceType);
for (Field field : fields) {
assert (field != null);
/**
* If clientFieldNames is non-null, use only the fields listed there
* to generate the signature. Otherwise, use all known fields.
*/
if ((clientFieldNames == null) || clientFieldNames.contains(field.getName())) {
crc.update(field.getName().getBytes(DEFAULT_ENCODING));
crc.update(getSerializedTypeName(field.getType()).getBytes(
DEFAULT_ENCODING));
}
}
Class<?> superClass = instanceType.getSuperclass();
if (superClass != null) {
generateSerializationSignature(superClass, crc, policy);
}
}
}
private static Class<?> getCustomFieldSerializer(ClassLoader classLoader,
String qualifiedSerialzierName) {
try {
Class<?> customSerializerClass = Class.forName(qualifiedSerialzierName,
false, classLoader);
return customSerializerClass;
} catch (ClassNotFoundException e) {
return null;
}
}
}