blob: 2599fdea08271e472caa0ae6231f2d9302fcc522 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.waveprotocol.box.server.rpc;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;
import org.waveprotocol.box.common.comms.WaveClientRpc.DocumentSnapshot;
import org.waveprotocol.box.common.comms.WaveClientRpc.ProtocolAuthenticate;
import org.waveprotocol.box.common.comms.WaveClientRpc.ProtocolAuthenticationResult;
import org.waveprotocol.box.common.comms.WaveClientRpc.ProtocolOpenRequest;
import org.waveprotocol.box.common.comms.WaveClientRpc.ProtocolSubmitRequest;
import org.waveprotocol.box.common.comms.WaveClientRpc.ProtocolSubmitResponse;
import org.waveprotocol.box.common.comms.WaveClientRpc.ProtocolWaveletUpdate;
import org.waveprotocol.box.common.comms.WaveClientRpc.WaveViewSnapshot;
import org.waveprotocol.box.common.comms.WaveClientRpc.WaveletSnapshot;
import org.waveprotocol.box.common.comms.proto.DocumentSnapshotProtoImpl;
import org.waveprotocol.box.common.comms.proto.ProtocolAuthenticateProtoImpl;
import org.waveprotocol.box.common.comms.proto.ProtocolAuthenticationResultProtoImpl;
import org.waveprotocol.box.common.comms.proto.ProtocolOpenRequestProtoImpl;
import org.waveprotocol.box.common.comms.proto.ProtocolSubmitRequestProtoImpl;
import org.waveprotocol.box.common.comms.proto.ProtocolSubmitResponseProtoImpl;
import org.waveprotocol.box.common.comms.proto.ProtocolWaveletUpdateProtoImpl;
import org.waveprotocol.box.common.comms.proto.WaveViewSnapshotProtoImpl;
import org.waveprotocol.box.common.comms.proto.WaveletSnapshotProtoImpl;
import org.waveprotocol.box.profile.ProfilesProto.ProfileResponse;
import org.waveprotocol.box.profile.proto.ProfileResponseProtoImpl;
import org.waveprotocol.box.search.SearchProto.SearchResponse;
import org.waveprotocol.box.search.proto.SearchResponseProtoImpl;
import org.waveprotocol.box.server.rpc.Rpc.CancelRpc;
import org.waveprotocol.box.server.rpc.Rpc.RpcFinished;
import org.waveprotocol.box.server.rpc.proto.CancelRpcProtoImpl;
import org.waveprotocol.box.server.rpc.proto.RpcFinishedProtoImpl;
import org.waveprotocol.box.attachment.AttachmentProto.AttachmentsResponse;
import org.waveprotocol.box.attachment.proto.AttachmentsResponseProtoImpl;
import org.waveprotocol.wave.communication.gson.GsonException;
import org.waveprotocol.wave.communication.gson.GsonSerializable;
import org.waveprotocol.wave.communication.json.RawStringData;
import org.waveprotocol.wave.communication.proto.ProtoWrapper;
import java.util.Map;
/**
* Serializes protos to/from JSON objects.
* <p>
* This class uses the PST-generated message classes to perform serialization
* and deserialization.
*/
public final class ProtoSerializer {
public static final class SerializationException extends Exception {
public SerializationException(Exception cause) {
super(cause);
}
public SerializationException(String message) {
super(message);
}
}
/**
* Serializes protos of a particular type to/from JSON objects.
*
* @param <P> proto type
* @param <D> GSON-based DTO wrapper type for a {@code P}
*/
static final class ProtoImplSerializer<
P extends Message,
D extends ProtoWrapper<P> & GsonSerializable> {
private final Class<P> protoClass;
private final Class<D> dtoClass;
ProtoImplSerializer(Class<P> protoClass, Class<D> dtoClass) {
this.protoClass = protoClass;
this.dtoClass = dtoClass;
}
static <P extends Message, D extends ProtoWrapper<P> & GsonSerializable>
ProtoImplSerializer<P, D> of(
Class<P> protoClass, Class<D> dtoClass) {
return new ProtoImplSerializer<P, D>(protoClass, dtoClass);
}
D newDto() throws SerializationException {
try {
return dtoClass.newInstance();
} catch (InstantiationException e) {
throw new SerializationException(e);
} catch (IllegalAccessException e) {
throw new SerializationException(e);
}
}
JsonElement toGson(MessageLite proto, RawStringData data, Gson gson)
throws SerializationException {
Preconditions.checkState(protoClass.isInstance(proto));
D dto = newDto();
dto.setPB(protoClass.cast(proto));
return dto.toGson(data, gson);
}
P fromJson(JsonElement json, RawStringData data, Gson gson) throws SerializationException {
D dto = newDto();
try {
dto.fromGson(json, gson, data);
} catch (GsonException e) {
throw new SerializationException(e);
}
return dto.getPB();
}
}
private final Gson gson = new Gson();
private final Map<Class<?>, ProtoImplSerializer<?, ?>> byClass = Maps.newHashMap();
private final Map<String, ProtoImplSerializer<?, ?>> byName = Maps.newHashMap();
public ProtoSerializer() {
init();
}
/** Adds the known proto types. */
private void init() {
// Note: this list is too inclusive, but has historically always been so.
// The real list only needs about 5 protos, since only top-level rpc types
// need to be here, not every single recursively reachable proto.
add(ProtocolAuthenticate.class, ProtocolAuthenticateProtoImpl.class);
add(ProtocolAuthenticationResult.class, ProtocolAuthenticationResultProtoImpl.class);
add(ProtocolOpenRequest.class, ProtocolOpenRequestProtoImpl.class);
add(ProtocolSubmitRequest.class, ProtocolSubmitRequestProtoImpl.class);
add(ProtocolSubmitResponse.class, ProtocolSubmitResponseProtoImpl.class);
add(ProtocolWaveletUpdate.class, ProtocolWaveletUpdateProtoImpl.class);
add(WaveletSnapshot.class, WaveletSnapshotProtoImpl.class);
add(DocumentSnapshot.class, DocumentSnapshotProtoImpl.class);
add(WaveViewSnapshot.class, WaveViewSnapshotProtoImpl.class);
add(CancelRpc.class, CancelRpcProtoImpl.class);
add(RpcFinished.class, RpcFinishedProtoImpl.class);
add(SearchResponse.class, SearchResponseProtoImpl.class);
add(ProfileResponse.class, ProfileResponseProtoImpl.class);
add(AttachmentsResponse.class, AttachmentsResponseProtoImpl.class);
}
/** Adds a binding between a proto class and a DTO message class. */
private <P extends Message, D extends ProtoWrapper<P> & GsonSerializable> void add(
Class<P> protoClass, Class<D> dtoClass) {
ProtoImplSerializer<P, D> serializer = ProtoImplSerializer.of(protoClass, dtoClass);
byClass.put(protoClass, serializer);
byName.put(protoClass.getSimpleName(), serializer);
}
/**
* Gets the serializer for a proto class. Never returns null.
*
* @throws SerializationException if there is no serializer for
* {@code protoClass}.
*/
private <P extends Message, D extends ProtoWrapper<P> & GsonSerializable> ProtoImplSerializer<P, D> getSerializer(Class<P> protoClass)
throws SerializationException {
@SuppressWarnings("unchecked")
// use of serializers map is safe.
ProtoImplSerializer<P, D> serializer =
(ProtoImplSerializer<P, D>) byClass.get(protoClass);
if (serializer == null) {
throw new SerializationException("Unknown proto class: " + protoClass.getName());
}
return serializer;
}
/**
* Gets the serializer for a proto class name. Never returns null.
*
* @throws SerializationException if there is no serializer for
* {@code protoName}.
*/
private <P extends Message> ProtoImplSerializer<P, ?> getSerializer(String protoName)
throws SerializationException {
@SuppressWarnings("unchecked")
// use of serializers map is safe.
ProtoImplSerializer<P, ?> serializer = (ProtoImplSerializer<P, ?>) byName.get(protoName);
if (serializer == null) {
throw new SerializationException("Unknown proto class: " + protoName);
}
return serializer;
}
/**
* Serializes a proto to JSON. Only protos whose classes have been registered
* will be serialized.
*
* @throws SerializationException if the class of {@code message} has not been
* registered.
*/
public <P extends Message>JsonElement toJson(P message) throws SerializationException {
return getSerializer(message.getClass()).toGson(message, null, gson);
}
/**
* Deserializes a proto from JSON. Only protos whose classes have been
* registered can be deserialized.
*
* @throws SerializationException if no class called {@code type} has been
* registered.
*/
public Message fromJson(JsonElement json, String type) throws SerializationException {
return getSerializer(type).fromJson(json, null, gson);
}
// Utility method for a test.
@VisibleForTesting
public <P extends Message> P fromJson(JsonElement json, Class<P> clazz)
throws SerializationException {
return getSerializer(clazz).fromJson(json, null, gson);
}
}