/*
* 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.beam.sdk.util;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.beam.sdk.util.CoderUtils.decodeFromByteArray;
import static org.apache.beam.sdk.util.CoderUtils.encodeToByteArray;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import org.apache.beam.sdk.coders.Coder;
import org.apache.beam.sdk.coders.CoderException;
import org.xerial.snappy.SnappyInputStream;
import org.xerial.snappy.SnappyOutputStream;
/**
* Utilities for working with Serializables.
*/
public class SerializableUtils {
/**
* Serializes the argument into an array of bytes, and returns it.
*
* @throws IllegalArgumentException if there are errors when serializing
*/
public static byte[] serializeToByteArray(Serializable value) {
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(new SnappyOutputStream(buffer))) {
oos.writeObject(value);
}
return buffer.toByteArray();
} catch (IOException exn) {
throw new IllegalArgumentException(
"unable to serialize " + value,
exn);
}
}
/**
* Deserializes an object from the given array of bytes, e.g., as
* serialized using {@link #serializeToByteArray}, and returns it.
*
* @throws IllegalArgumentException if there are errors when
* deserializing, using the provided description to identify what
* was being deserialized
*/
public static Object deserializeFromByteArray(byte[] encodedValue,
String description) {
try {
try (ObjectInputStream ois = new ObjectInputStream(
new SnappyInputStream(new ByteArrayInputStream(encodedValue)))) {
return ois.readObject();
}
} catch (IOException | ClassNotFoundException exn) {
throw new IllegalArgumentException(
"unable to deserialize " + description,
exn);
}
}
public static <T extends Serializable> T ensureSerializable(T value) {
@SuppressWarnings("unchecked")
T copy = (T) deserializeFromByteArray(serializeToByteArray(value),
value.toString());
return copy;
}
public static <T extends Serializable> T clone(T value) {
@SuppressWarnings("unchecked")
T copy = (T) deserializeFromByteArray(serializeToByteArray(value),
value.toString());
return copy;
}
/**
* Serializes a Coder and verifies that it can be correctly deserialized.
*
* <p>Throws a RuntimeException if serialized Coder cannot be deserialized, or
* if the deserialized instance is not equal to the original.
*
* @return the deserialized Coder
*/
public static Coder<?> ensureSerializable(Coder<?> coder) {
// Make sure that Coders are java serializable as well since
// they are regularly captured within DoFn's.
Coder<?> copy = (Coder<?>) ensureSerializable((Serializable) coder);
checkState(
coder.equals(copy),
"Coder not equal to original after serialization, indicating that the Coder may not "
+ "implement serialization correctly. Before: %s, after: %s",
coder,
copy);
return copy;
}
/**
* Serializes an arbitrary T with the given {@code Coder<T>} and verifies
* that it can be correctly deserialized.
*/
public static <T> T ensureSerializableByCoder(
Coder<T> coder, T value, String errorContext) {
byte[] encodedValue;
try {
encodedValue = encodeToByteArray(coder, value);
} catch (CoderException exn) {
// TODO: Put in better element printing:
// truncate if too long.
throw new IllegalArgumentException(
errorContext + ": unable to encode value "
+ value + " using " + coder,
exn);
}
try {
return decodeFromByteArray(coder, encodedValue);
} catch (CoderException exn) {
// TODO: Put in better encoded byte array printing:
// use printable chars with escapes instead of codes, and
// truncate if too long.
throw new IllegalArgumentException(
errorContext + ": unable to decode " + Arrays.toString(encodedValue)
+ ", encoding of value " + value + ", using " + coder,
exn);
}
}
}