blob: feac7a12d13129a1fa4a37d653baf02d29804cfa [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.apache.geode.internal.protocol.protobuf.v1;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import com.google.protobuf.ByteString;
import com.google.protobuf.NullValue;
import org.apache.geode.annotations.Experimental;
import org.apache.geode.internal.protocol.protobuf.v1.serialization.JsonPdxConverter;
import org.apache.geode.internal.protocol.protobuf.v1.serialization.SerializationService;
import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.DecodingException;
import org.apache.geode.internal.protocol.protobuf.v1.serialization.exception.EncodingException;
import org.apache.geode.internal.protocol.protobuf.v1.utilities.exception.UnknownProtobufEncodingType;
import org.apache.geode.internal.protocol.serialization.NoOpCustomValueSerializer;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.protocol.serialization.ValueSerializer;
@Experimental
public class ProtobufSerializationService implements SerializationService<BasicTypes.EncodedValue> {
private final JsonPdxConverter jsonPdxConverter = new JsonPdxConverter();
private final ValueSerializer serializer;
public ProtobufSerializationService() {
this(new NoOpCustomValueSerializer());
}
public ProtobufSerializationService(ValueSerializer valueSerializer) {
this.serializer = valueSerializer;
}
/**
* @param value the value to be encoded
*
* @return EncodedValue message with the serialized value
*/
@Override
public BasicTypes.EncodedValue encode(Object value) throws EncodingException {
BasicTypes.EncodedValue.Builder builder = BasicTypes.EncodedValue.newBuilder();
try {
if (serializer.supportsPrimitives()) {
ByteString encoded = serializer.serialize(value);
return builder.setCustomObjectResult(encoded).build();
}
if (value == null) {
return builder.setNullResult(NullValue.NULL_VALUE).build();
}
ProtobufEncodingTypes protobufEncodingTypes = ProtobufEncodingTypes.valueOf(value.getClass());
switch (protobufEncodingTypes) {
case INT: {
builder.setIntResult((Integer) value);
break;
}
case LONG: {
builder.setLongResult((Long) value);
break;
}
case SHORT: {
builder.setShortResult((Short) value);
break;
}
case BYTE: {
builder.setByteResult((Byte) value);
break;
}
case DOUBLE: {
builder.setDoubleResult((Double) value);
break;
}
case FLOAT: {
builder.setFloatResult((Float) value);
break;
}
case BINARY: {
builder.setBinaryResult(ByteString.copyFrom((byte[]) value));
break;
}
case BOOLEAN: {
builder.setBooleanResult((Boolean) value);
break;
}
case STRING: {
builder.setStringResult((String) value);
break;
}
default: {
ByteString customResult = customSerialize(value);
if (customResult != null) {
builder.setCustomObjectResult(customResult);
} else if (value instanceof PdxInstance) {
builder.setJsonObjectResult(jsonPdxConverter.encode((PdxInstance) value));
} else {
throw new EncodingException("No handler for object type " + value.getClass());
}
break;
}
}
} catch (UnknownProtobufEncodingType e) {
throw new EncodingException("No protobuf encoding for type " + value.getClass().getName(), e);
} catch (IOException e) {
throw new EncodingException("Error encoding type " + value.getClass().getName(), e);
}
return builder.build();
}
private ByteString customSerialize(Object value) throws IOException {
return serializer instanceof NoOpCustomValueSerializer ? null : serializer.serialize(value);
}
/**
* @param encodedValue - The value to be decoded
* @return A decoded object representing encodedValue
* @throws EncodingException if the value cannot be decoded.
*/
@Override
public Object decode(BasicTypes.EncodedValue encodedValue) throws DecodingException {
try {
switch (encodedValue.getValueCase()) {
case BINARYRESULT:
return encodedValue.getBinaryResult().toByteArray();
case BOOLEANRESULT:
return encodedValue.getBooleanResult();
case BYTERESULT:
return (byte) encodedValue.getByteResult();
case DOUBLERESULT:
return encodedValue.getDoubleResult();
case FLOATRESULT:
return encodedValue.getFloatResult();
case INTRESULT:
return encodedValue.getIntResult();
case LONGRESULT:
return encodedValue.getLongResult();
case SHORTRESULT:
return (short) encodedValue.getShortResult();
case STRINGRESULT:
return encodedValue.getStringResult();
case JSONOBJECTRESULT:
return jsonPdxConverter.decode(encodedValue.getJsonObjectResult());
case NULLRESULT:
return null;
case CUSTOMOBJECTRESULT:
return serializer.deserialize(encodedValue.getCustomObjectResult());
default:
throw new DecodingException(
"Unknown Protobuf encoding type: " + encodedValue.getValueCase());
}
} catch (IOException | ClassNotFoundException e) {
throw new DecodingException("Error decoding value", e);
}
}
public Collection<Object> decodeList(Collection<BasicTypes.EncodedValue> encodedValues) {
return encodedValues.stream().map(this::decode).collect(Collectors.toList());
}
/**
* Maps classes to encoding for protobuf.
*
* This currently conflates object type with serialization, which may be an issue if we add more
* types of object serialization.
*/
private enum ProtobufEncodingTypes {
// If any classes are added to this list that are not final, the logic
// in valueOf must change. It currently works only if the user's class
// is exactly the same as the class listed here.
STRING(String.class),
INT(Integer.class),
LONG(Long.class),
SHORT(Short.class),
BYTE(Byte.class),
BOOLEAN(Boolean.class),
DOUBLE(Double.class),
FLOAT(Float.class),
BINARY(byte[].class),
OTHER(Object.class);
private Class clazz;
private static Map<Class, ProtobufEncodingTypes> classToType = new HashMap<>();
static {
for (ProtobufEncodingTypes type : values()) {
classToType.put(type.clazz, type);
}
}
ProtobufEncodingTypes(Class clazz) {
this.clazz = clazz;
}
public static ProtobufEncodingTypes valueOf(Class unencodedValueClass)
throws UnknownProtobufEncodingType {
ProtobufEncodingTypes type = classToType.get(unencodedValueClass);
if (type != null) {
return type;
} else {
return OTHER;
}
}
}
}