blob: eb583dec522fc08252ec5706d0e8b68cf9a1d502 [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.serialization.internal;
import static org.apache.geode.internal.serialization.DataSerializableFixedID.NO_FIXED_ID;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.IOException;
import java.io.NotSerializableException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketException;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.internal.serialization.BasicSerializable;
import org.apache.geode.internal.serialization.DSCODE;
import org.apache.geode.internal.serialization.DSFIDNotFoundException;
import org.apache.geode.internal.serialization.DSFIDSerializer;
import org.apache.geode.internal.serialization.DataSerializableFixedID;
import org.apache.geode.internal.serialization.DeserializationContext;
import org.apache.geode.internal.serialization.DscodeHelper;
import org.apache.geode.internal.serialization.KnownVersion;
import org.apache.geode.internal.serialization.ObjectDeserializer;
import org.apache.geode.internal.serialization.ObjectSerializer;
import org.apache.geode.internal.serialization.SerializationContext;
import org.apache.geode.internal.serialization.SerializationVersions;
import org.apache.geode.internal.serialization.StaticSerialization;
public class DSFIDSerializerImpl implements DSFIDSerializer {
@Immutable
private final Constructor<?>[] dsfidMap = new Constructor<?>[256];
@Immutable("This maybe should be wrapped in an unmodifiableMap?")
private final Int2ObjectOpenHashMap<Constructor<?>> dsfidMap2 = new Int2ObjectOpenHashMap<>(800);
private final ObjectSerializer objectSerializer;
private final ObjectDeserializer objectDeserializer;
public DSFIDSerializerImpl() {
objectSerializer = createDefaultObjectSerializer();
objectDeserializer = createDefaultObjectDeserializer();
}
public DSFIDSerializerImpl(ObjectSerializer objectSerializer,
ObjectDeserializer objectDeserializer) {
this.objectSerializer =
objectSerializer == null ? createDefaultObjectSerializer() : objectSerializer;
this.objectDeserializer =
objectDeserializer == null ? createDefaultObjectDeserializer() : objectDeserializer;
}
private ObjectSerializer createDefaultObjectSerializer() {
return new ObjectSerializer() {
@Override
public void writeObject(Object obj, DataOutput output) throws IOException {
if (obj == null || obj instanceof BasicSerializable) {
write((BasicSerializable) obj, output);
} else {
throw new NotSerializableException("object with class " + obj.getClass().getName() +
" is not serializable via DSFIDSerializerImpl");
}
}
@Override
public void invokeToData(Object ds, DataOutput out) throws IOException {
DSFIDSerializerImpl.this.invokeToData(ds, out);
}
};
}
private ObjectDeserializer createDefaultObjectDeserializer() {
return new ObjectDeserializer() {
@SuppressWarnings("unchecked")
@Override
public <T> T readObject(DataInput input) throws IOException, ClassNotFoundException {
return (T) readDSFID(input);
}
@Override
public void invokeFromData(Object ds, DataInput in)
throws IOException, ClassNotFoundException {
DSFIDSerializerImpl.this.invokeFromData(ds, in);
}
};
}
@Override
public ObjectSerializer getObjectSerializer() {
return objectSerializer;
}
@Override
public ObjectDeserializer getObjectDeserializer() {
return objectDeserializer;
}
// Writes just the header of a DataSerializableFixedID to out.
@Override
public void writeDSFIDHeader(int dsfid, DataOutput out) throws IOException {
if (dsfid == DataSerializableFixedID.ILLEGAL) {
throw new IllegalStateException(
"attempted to serialize ILLEGAL dsfid");
}
if (dsfid <= Byte.MAX_VALUE && dsfid >= Byte.MIN_VALUE) {
out.writeByte(DSCODE.DS_FIXED_ID_BYTE.toByte());
out.writeByte(dsfid);
} else if (dsfid <= Short.MAX_VALUE && dsfid >= Short.MIN_VALUE) {
out.writeByte(DSCODE.DS_FIXED_ID_SHORT.toByte());
out.writeShort(dsfid);
} else {
out.writeByte(DSCODE.DS_FIXED_ID_INT.toByte());
out.writeInt(dsfid);
}
}
@Override
public void write(final BasicSerializable bs, final DataOutput out) throws IOException {
writeMetaData(bs, out);
if (bs != null) {
invokeToData(bs, out);
}
}
private void writeMetaData(final BasicSerializable bs, final DataOutput out)
throws IOException {
if (bs == null) {
out.writeByte(DSCODE.NULL.toByte());
return;
}
if (bs instanceof DataSerializableFixedID) {
final DataSerializableFixedID dsfid = (DataSerializableFixedID) bs;
final int id = dsfid.getDSFID();
if (id == NO_FIXED_ID) {
throw new IllegalArgumentException(
"NO_FIXED_ID is not supported by BasicDSFIDSerializer - use InternalDataSerializer instead: "
+ dsfid.getClass().getName());
}
writeDSFIDHeader(id, out);
} else {
out.writeByte(DSCODE.DATA_SERIALIZABLE.toByte());
final Class<?> c = bs.getClass();
StaticSerialization.writeClass(c, out);
}
}
/**
* For backward compatibility this method should be used to invoke toData on a DSFID.
* It will invoke the correct toData method based on the class's version
* information. This method does not write information about the class of the object. When
* deserializing use the method invokeFromData to read the contents of the object.
*
* @param ds the object to write
* @param out the output stream.
*/
@Override
public void invokeToData(Object ds, DataOutput out) throws IOException {
final SerializationContext context = new SerializationContextImpl(out, this);
boolean isDSFID = ds instanceof DataSerializableFixedID;
if (!isDSFID) {
if (ds instanceof BasicSerializable) {
((BasicSerializable) ds).toData(out, context);
return;
}
throw new IllegalArgumentException(
"Expected a DataSerializableFixedID but found " + ds.getClass().getName());
}
try {
boolean invoked = false;
KnownVersion v = context.getSerializationVersion();
if (!KnownVersion.CURRENT.equals(v)) {
// get versions where DataOutput was upgraded
SerializationVersions sv = (SerializationVersions) ds;
KnownVersion[] versions = sv.getSerializationVersions();
// check if the version of the peer or diskstore is different and
// there has been a change in the message
if (versions != null) {
for (KnownVersion version : versions) {
// if peer version is less than the greatest upgraded version
if (v.compareTo(version) < 0) {
ds.getClass().getMethod("toDataPre_" + version.getMethodSuffix(),
new Class[] {DataOutput.class, SerializationContext.class})
.invoke(ds, out, context);
invoked = true;
break;
}
}
}
}
if (!invoked) {
((DataSerializableFixedID) ds).toData(out, context);
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException
| InvocationTargetException e) {
throw new IOException(
"problem invoking toData method on object of class" + ds.getClass().getName(), e);
}
}
private Object readDSFID(final DataInput in) throws IOException, ClassNotFoundException {
checkIn(in);
DSCODE dsHeaderType = DscodeHelper.toDSCODE(in.readByte());
if (dsHeaderType == DSCODE.NULL) {
return null;
}
return readDSFID(in, dsHeaderType);
}
private Object readDSFID(final DataInput in, DSCODE dscode)
throws IOException, ClassNotFoundException {
switch (dscode) {
case DS_FIXED_ID_BYTE:
return create(in.readByte(), in);
case DS_FIXED_ID_SHORT:
return create(in.readShort(), in);
case DS_NO_FIXED_ID:
throw new IllegalStateException(
"DS_NO_FIXED_ID is not supported in readDSFID - use InternalDataSerializer instead");
case DS_FIXED_ID_INT:
return create(in.readInt(), in);
case DATA_SERIALIZABLE:
return readDataSerializable(in);
default:
throw new IllegalStateException("unexpected byte: " + dscode + " while reading dsfid");
}
}
private int readDSFIDHeader(final DataInput in, DSCODE dscode) throws IOException {
switch (dscode) {
case DS_FIXED_ID_BYTE:
return in.readByte();
case DS_FIXED_ID_SHORT:
return in.readShort();
case DS_FIXED_ID_INT:
return in.readInt();
default:
throw new IllegalStateException("unexpected byte: " + dscode + " while reading dsfid");
}
}
@Override
public int readDSFIDHeader(final DataInput in) throws IOException {
checkIn(in);
return readDSFIDHeader(in, DscodeHelper.toDSCODE(in.readByte()));
}
/**
* Checks to make sure a {@code DataInput} is not {@code null}.
*
* @throws NullPointerException If {@code in} is {@code null}
*/
public static void checkIn(DataInput in) {
if (in == null) {
throw new NullPointerException("Null DataInput");
}
}
/**
* For backward compatibility this method should be used to invoke fromData on a DSFID or
* DataSerializable. It will invoke the correct fromData method based on the class's version
* information. This method does not read information about the class of the object. When
* serializing use the method invokeToData to write the contents of the object.
*
* @param ds the object to write
* @param in the input stream.
*/
@Override
public void invokeFromData(Object ds, DataInput in)
throws IOException, ClassNotFoundException {
DeserializationContextImpl context = new DeserializationContextImpl(in, this);
try {
boolean invoked = false;
KnownVersion v = context.getSerializationVersion();
if (!KnownVersion.CURRENT.equals(v) && ds instanceof SerializationVersions) {
// get versions where DataOutput was upgraded
SerializationVersions vds = (SerializationVersions) ds;
KnownVersion[] versions = vds.getSerializationVersions();
// check if the version of the peer or diskstore is different and
// there has been a change in the message
if (versions != null) {
for (KnownVersion version : versions) {
// if peer version is less than the greatest upgraded version
if (v.compareTo(version) < 0) {
ds.getClass().getMethod("fromDataPre" + '_' + version.getMethodSuffix(),
new Class[] {DataInput.class, DeserializationContext.class})
.invoke(ds, in, context);
invoked = true;
break;
}
}
}
}
if (!invoked) {
if (ds instanceof BasicSerializable) {
((BasicSerializable) ds).fromData(in, context);
} else {
throw new IOException(
"problem invoking fromData method on object of class" + ds.getClass().getName());
}
}
} catch (EOFException | ClassNotFoundException | SocketException ex) {
// client went away - ignore
throw ex;
} catch (RuntimeException e) {
throw e;
} catch (Exception ex) {
throw new IOException(
String.format("Could not create an instance of %s .",
ds.getClass().getName()),
ex);
}
}
@Override
public void registerDSFID(int dsfid, Class<?> dsfidClass) {
try {
Constructor<?> cons = dsfidClass.getConstructor((Class<Object>[]) null);
cons.setAccessible(true);
if (!cons.isAccessible()) {
throw new IllegalArgumentException(
"default constructor not accessible " + "for DSFID=" + dsfid + ": " + dsfidClass);
}
if (dsfid >= Byte.MIN_VALUE && dsfid <= Byte.MAX_VALUE) {
dsfidMap[dsfid + Byte.MAX_VALUE + 1] = cons;
} else {
dsfidMap2.put(dsfid, cons);
}
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException("Unable to find a default constructor for " + dsfidClass,
nsme);
}
}
public Object create(int dsfid, DataInput in) throws IOException, ClassNotFoundException {
final Constructor<?> cons;
if (dsfid >= Byte.MIN_VALUE && dsfid <= Byte.MAX_VALUE) {
cons = dsfidMap[dsfid + Byte.MAX_VALUE + 1];
} else {
cons = dsfidMap2.get(dsfid);
}
if (cons != null) {
try {
Object ds = cons.newInstance((Object[]) null);
invokeFromData(ds, in);
return ds;
} catch (InstantiationException | IllegalAccessException ie) {
throw new IOException(ie.getMessage(), ie);
} catch (InvocationTargetException ite) {
Throwable targetEx = ite.getTargetException();
if (targetEx instanceof IOException) {
throw (IOException) targetEx;
} else if (targetEx instanceof ClassNotFoundException) {
throw (ClassNotFoundException) targetEx;
} else {
throw new IOException(ite.getMessage(), targetEx);
}
}
}
throw new DSFIDNotFoundException("Unknown DataSerializableFixedID: " + dsfid, dsfid);
}
private Object readDataSerializable(final DataInput in)
throws IOException, ClassNotFoundException {
Class<?> c = StaticSerialization.readClass(in);
try {
Constructor<?> init = c.getConstructor();
init.setAccessible(true);
Object o = init.newInstance();
invokeFromData(o, in);
return o;
} catch (EOFException | SocketException ex) {
// client went away - ignore
throw ex;
} catch (Exception ex) {
throw new IOException(
String.format("Could not create an instance of %s .",
c.getName()),
ex);
}
}
public Constructor<?>[] getDsfidmap() {
return dsfidMap;
}
public Int2ObjectOpenHashMap<Constructor<?>> getDsfidmap2() {
return dsfidMap2;
}
@Override
public SerializationContext createSerializationContext(DataOutput dataOutput) {
return new SerializationContextImpl(dataOutput, this);
}
@Override
public DeserializationContext createDeserializationContext(DataInput dataInput) {
return new DeserializationContextImpl(dataInput, this);
}
}