/** * 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); } }