blob: 9143ed54575247e0da6c6bbc773633d9cea361e8 [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;
import com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy;
import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* API for loading a {@link SerializationPolicy}.
*/
public final class SerializationPolicyLoader {
/**
* Keyword for listing the serializable fields of an enchanced class that are
* visible to client code.
*/
public static final String CLIENT_FIELDS_KEYWORD = "@ClientFields";
/**
* Default encoding for serialization policy files.
*/
public static final String SERIALIZATION_POLICY_FILE_ENCODING = "UTF-8";
private static final String FORMAT_ERROR_MESSAGE = "Expected: className, "
+ "[true | false], [true | false], [true | false], [true | false], typeId, signature";
/**
* Returns the serialization policy file name from the serialization
* policy strong name.
*
* @param serializationPolicyStrongName the serialization policy strong name
* @return the serialization policy file name from the serialization
* policy strong name
*/
public static String getSerializationPolicyFileName(
String serializationPolicyStrongName) {
return serializationPolicyStrongName + ".gwt.rpc";
}
/**
* Loads a SerializationPolicy from an input stream.
*
* @param inputStream stream to load from
* @return a {@link SerializationPolicy} loaded from the input stream
*
* @throws IOException if an error occurs while reading the stream
* @throws ParseException if the input stream is not properly formatted
* @throws ClassNotFoundException if a class specified in the serialization
* policy cannot be loaded
*
* @deprecated see {@link #loadFromStream(InputStream, List)}
*/
@Deprecated
public static SerializationPolicy loadFromStream(InputStream inputStream)
throws IOException, ParseException, ClassNotFoundException {
List<ClassNotFoundException> classNotFoundExceptions = new ArrayList<ClassNotFoundException>();
SerializationPolicy serializationPolicy = loadFromStream(inputStream,
classNotFoundExceptions);
if (!classNotFoundExceptions.isEmpty()) {
// Just report the first failure.
throw classNotFoundExceptions.get(0);
}
return serializationPolicy;
}
/**
* Loads a SerializationPolicy from an input stream and optionally record any
* {@link ClassNotFoundException}s.
*
* @param inputStream stream to load the SerializationPolicy from.
* @param classNotFoundExceptions if not <code>null</code>, all of the
* {@link ClassNotFoundException}s thrown while loading this
* serialization policy will be added to this list
* @return a {@link SerializationPolicy} loaded from the input stream.
*
* @throws IOException if an error occurs while reading the stream
* @throws ParseException if the input stream is not properly formatted
*/
public static SerializationPolicy loadFromStream(InputStream inputStream,
List<ClassNotFoundException> classNotFoundExceptions) throws IOException,
ParseException {
if (inputStream == null) {
throw new NullPointerException("inputStream");
}
Map<Class<?>, Boolean> whitelistSer = new HashMap<Class<?>, Boolean>();
Map<Class<?>, Boolean> whitelistDeser = new HashMap<Class<?>, Boolean>();
Map<Class<?>, String> typeIds = new HashMap<Class<?>, String>();
Map<Class<?>, Set<String>> clientFields = new HashMap<Class<?>, Set<String>>();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
InputStreamReader isr = new InputStreamReader(inputStream,
SERIALIZATION_POLICY_FILE_ENCODING);
BufferedReader br = new BufferedReader(isr);
String line = br.readLine();
int lineNum = 1;
while (line != null) {
line = line.trim();
if (line.length() > 0) {
String[] components = line.split(",");
if (components[0].equals(CLIENT_FIELDS_KEYWORD)) {
/*
* Lines starting with '@ClientFields' list potentially serializable fields known to
* client code for classes that may be enhanced with additional fields on the server.
* If additional server fields are found, they will be serizalized separately from the
* normal RPC process and transmitted to the client as an opaque blob of data stored
* in a WeakMapping associated with the object instance.
*/
String binaryTypeName = components[1].trim();
Class<?> clazz;
try {
clazz = Class.forName(binaryTypeName, false, contextClassLoader);
HashSet<String> fieldNames = new HashSet<String>();
for (int i = 2; i < components.length; i++) {
fieldNames.add(components[i]);
}
clientFields.put(clazz, fieldNames);
} catch (ClassNotFoundException ex) {
// Ignore the error, but add it to the list of errors if one was
// provided.
if (classNotFoundExceptions != null) {
classNotFoundExceptions.add(ex);
}
}
} else {
if (components.length != 2 && components.length != 7) {
throw new ParseException(FORMAT_ERROR_MESSAGE, lineNum);
}
for (int i = 0; i < components.length; i++) {
components[i] = components[i].trim();
if (components[i].length() == 0) {
throw new ParseException(FORMAT_ERROR_MESSAGE, lineNum);
}
}
String binaryTypeName = components[0].trim();
boolean fieldSer;
boolean instantSer;
boolean fieldDeser;
boolean instantDeser;
String typeId;
if (components.length == 2) {
fieldSer = fieldDeser = true;
instantSer = instantDeser = Boolean.valueOf(components[1]);
typeId = binaryTypeName;
} else {
int idx = 1;
// TODO: Validate the instantiable string better.
fieldSer = Boolean.valueOf(components[idx++]);
instantSer = Boolean.valueOf(components[idx++]);
fieldDeser = Boolean.valueOf(components[idx++]);
instantDeser = Boolean.valueOf(components[idx++]);
typeId = components[idx++];
if (!fieldSer && !fieldDeser
&& !TypeNameObfuscator.SERVICE_INTERFACE_ID.equals(typeId)) {
throw new ParseException("Type " + binaryTypeName
+ " is neither field serializable, field deserializable "
+ "nor the service interface", lineNum);
}
}
try {
Class<?> clazz = Class.forName(binaryTypeName, false,
contextClassLoader);
if (fieldSer) {
whitelistSer.put(clazz, instantSer);
}
if (fieldDeser) {
whitelistDeser.put(clazz, instantDeser);
}
typeIds.put(clazz, typeId);
} catch (ClassNotFoundException ex) {
// Ignore the error, but add it to the list of errors if one was
// provided.
if (classNotFoundExceptions != null) {
classNotFoundExceptions.add(ex);
}
}
}
}
line = br.readLine();
lineNum++;
}
return new StandardSerializationPolicy(whitelistSer, whitelistDeser,
typeIds, clientFields);
}
private SerializationPolicyLoader() {
}
}