/* * Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group * * Licensed 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 one.nio.serial; import one.nio.gen.BytecodeGenerator; import one.nio.mgt.Management; import one.nio.util.Base64; import one.nio.util.JavaInternals; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.Externalizable; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class Repository { public static final Log log = LogFactory.getLog(Repository.class); static final IdentityHashMap<Class, Serializer> classMap = new IdentityHashMap<Class, Serializer>(128); static final HashMap<Long, Serializer> uidMap = new HashMap<Long, Serializer>(128); static final HashMap<Method, MethodSerializer> methodMap = new HashMap<Method, MethodSerializer>(); static final Serializer[] bootstrapSerializers = new Serializer[128]; static final IdentityHashMap<Class, Integer> serializationOptions = new IdentityHashMap<Class, Integer>(); static final HashMap<String, Class> renamedClasses = new HashMap<String, Class>(); static final AtomicInteger anonymousClasses = new AtomicInteger(); static final int ENUM = 0x4000; public static final MethodSerializer provide = registerMethod(JavaInternals.getMethod(Repository.class, "provideSerializer", Serializer.class)); public static final MethodSerializer request = registerMethod(JavaInternals.getMethod(Repository.class, "requestSerializer", long.class)); public static final int SKIP_READ_OBJECT = 1; public static final int SKIP_WRITE_OBJECT = 2; public static final int SKIP_CUSTOM_SERIALIZATION = SKIP_READ_OBJECT | SKIP_WRITE_OBJECT; public static final int INLINE = 4; public static final int FIELD_SERIALIZATION = 8; public static final int ARRAY_STUBS = 1; public static final int COLLECTION_STUBS = 2; public static final int MAP_STUBS = 4; public static final int ENUM_STUBS = 8; public static final int CUSTOM_STUBS = 16; public static final int DEFAULT_OPTIONS = ARRAY_STUBS | COLLECTION_STUBS | MAP_STUBS | ENUM_STUBS | CUSTOM_STUBS; private static long nextBootstrapUid = -10; private static int options = Integer.getInteger("one.nio.serial.options", DEFAULT_OPTIONS); static { addBootstrap(new IntegerSerializer()); addBootstrap(new LongSerializer()); addBootstrap(new BooleanSerializer()); addBootstrap(new ByteSerializer()); addBootstrap(new ShortSerializer()); addBootstrap(new CharacterSerializer()); addBootstrap(new FloatSerializer()); addBootstrap(new DoubleSerializer()); addBootstrap(new StringSerializer()); addBootstrap(new DateSerializer()); addBootstrap(new ClassSerializer()); addBootstrap(new BitSetSerializer()); addBootstrap(new BooleanArraySerializer()); addBootstrap(new ByteArraySerializer()); addBootstrap(new ShortArraySerializer()); addBootstrap(new CharacterArraySerializer()); addBootstrap(new IntegerArraySerializer()); addBootstrap(new LongArraySerializer()); addBootstrap(new FloatArraySerializer()); addBootstrap(new DoubleArraySerializer()); addBootstrap(new ObjectArraySerializer(Object[].class)); addBootstrap(new ObjectArraySerializer(String[].class)); addBootstrap(new ObjectArraySerializer(Class[].class)); addBootstrap(new ObjectArraySerializer(Long[].class)); addBootstrap(new ArrayListSerializer()); addBootstrap(new CollectionSerializer(LinkedList.class)); addBootstrap(new CollectionSerializer(Vector.class)); addBootstrap(new CollectionSerializer(HashSet.class)); addBootstrap(new CollectionSerializer(TreeSet.class)); addBootstrap(new CollectionSerializer(LinkedHashSet.class)); addBootstrap(new HashMapSerializer()); addBootstrap(new MapSerializer(TreeMap.class)); addBootstrap(new MapSerializer(LinkedHashMap.class)); addBootstrap(new MapSerializer(Hashtable.class)); addBootstrap(new MapSerializer(IdentityHashMap.class)); addBootstrap(new MapSerializer(ConcurrentHashMap.class)); addBootstrap(new SerializerSerializer(ObjectArraySerializer.class)); addBootstrap(new SerializerSerializer(EnumSerializer.class)); addBootstrap(new SerializerSerializer(CollectionSerializer.class)); addBootstrap(new SerializerSerializer(MapSerializer.class)); addBootstrap(new SerializerSerializer(ExternalizableSerializer.class)); addBootstrap(new SerializerSerializer(GeneratedSerializer.class)); addBootstrap(new ExternalizableSerializer(SerializerNotFoundException.class)); addOptionalBootstrap("one.app.remote.reflect.MethodId"); addOptionalBootstrap("one.app.remote.comp.RemoteMethodCallRequest"); addBootstrap(new TimestampSerializer()); addBootstrap(new RemoteCallSerializer()); addBootstrap(new SerializerSerializer(MethodSerializer.class)); // Unable to run readObject/writeObject for the following classes. // Fortunately standard serialization works well for them. setOptions(InetAddress.class, SKIP_CUSTOM_SERIALIZATION); setOptions(InetSocketAddress.class, SKIP_CUSTOM_SERIALIZATION); setOptions(StringBuilder.class, SKIP_CUSTOM_SERIALIZATION); setOptions(StringBuffer.class, SKIP_CUSTOM_SERIALIZATION); setOptions(BigInteger.class, SKIP_CUSTOM_SERIALIZATION); // At some moment InetAddress fields were moved to an auxilary holder class. // This resolves backward compatibility problem by inlining holder fields during serialization. setOptions("java.net.InetAddress$InetAddressHolder", INLINE); setOptions("java.net.InetSocketAddress$InetSocketAddressHolder", INLINE); Management.registerMXBean(new SerializationMXBeanImpl(), "one.nio.serial:type=Serialization"); } private static void addBootstrap(Serializer serializer) { serializer.uid = nextBootstrapUid--; provideSerializer(serializer); } private static void addOptionalBootstrap(String className) { try { addBootstrap(generateFor(Class.forName(className))); } catch (ClassNotFoundException e) { // Optional class is missing. Skip the slot to maintain the order of other bootstrap serializers. log.warn("Missing optional bootstrap class: " + className); nextBootstrapUid--; } } @SuppressWarnings("unchecked") public static <T> Serializer<T> get(Class<T> cls) { Serializer result = classMap.get(cls); return result != null ? result : generateFor(cls); } public static MethodSerializer get(Method method) { return methodMap.get(method); } public static MethodSerializer registerMethod(Method method) { MethodSerializer result = methodMap.get(method); if (result == null) { result = new MethodSerializer(method); provideSerializer(result); } return result; } public static boolean preload(Class... classes) { for (Class cls : classes) { get(cls); } return true; } public static Serializer requestSerializer(long uid) throws SerializerNotFoundException { Serializer result = uidMap.get(uid); if (result != null) { return result; } throw new SerializerNotFoundException(uid); } public static Serializer requestBootstrapSerializer(byte uid) { return bootstrapSerializers[128 + uid]; } public static synchronized void provideSerializer(Serializer serializer) { Serializer oldSerializer = uidMap.put(serializer.uid, serializer); if (oldSerializer != null && oldSerializer.cls != serializer.cls) { throw new IllegalStateException("UID collision: " + serializer.descriptor + " overwrites " + oldSerializer.descriptor); } if (serializer.uid < 0) { bootstrapSerializers[128 + (int) serializer.uid] = serializer; } if (serializer.origin != Origin.EXTERNAL) { if (serializer instanceof MethodSerializer) { MethodSerializer methodSerializer = (MethodSerializer) serializer; methodMap.put(methodSerializer.method, methodSerializer); } else { classMap.put(serializer.cls, serializer); } } } public static void provideSerializer(String base64) { try { byte[] serialForm = Base64.decodeFromChars(base64.toCharArray()); provideSerializer((Serializer) Serializer.deserialize(serialForm)); } catch (Exception e) { throw new IllegalArgumentException(e); } } public static void setOptions(String className, int options) { try { Class cls = Class.forName(className, false, BytecodeGenerator.INSTANCE); setOptions(cls, options); } catch (ClassNotFoundException e) { // Ignore } } public static synchronized void setOptions(Class cls, int options) { serializationOptions.put(cls, options); } public static boolean hasOptions(Class cls, int options) { Integer value = serializationOptions.get(cls); return value != null && (value & options) == options; } public static void setOptions(int options) { Repository.options = options; } public static int getOptions() { return options; } public static byte[] saveSnapshot() throws IOException { Serializer[] serializers = getSerializers(uidMap); CalcSizeStream css = new CalcSizeStream(); for (Serializer serializer : serializers) { if (serializer.uid >= 0) { css.writeObject(serializer); } } byte[] snapshot = new byte[css.count()]; SerializeStream ss = new SerializeStream(snapshot); for (Serializer serializer : serializers) { if (serializer.uid >= 0) { ss.writeObject(serializer); } } return snapshot; } public static void saveSnapshot(String fileName) throws IOException { byte[] snapshot = saveSnapshot(); FileOutputStream fos = new FileOutputStream(fileName); try { fos.write(snapshot); } finally { fos.close(); } } public static int loadSnapshot(byte[] snapshot) throws IOException, ClassNotFoundException { int count = 0; DeserializeStream ds = new DeserializeStream(snapshot); while (ds.available() > 0) { provideSerializer((Serializer) ds.readObject()); count++; } return count; } public static int loadSnapshot(String fileName) throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream(fileName); try { byte[] snapshot = new byte[fis.available()]; fis.read(snapshot); return loadSnapshot(snapshot); } finally { fis.close(); } } private static synchronized Serializer[] getSerializers(Map<?, ? extends Serializer> map) { return map.values().toArray(new Serializer[map.size()]); } private static synchronized Serializer generateFor(Class<?> cls) { Serializer serializer = classMap.get(cls); if (serializer == null) { if (cls.isArray()) { get(cls.getComponentType()); serializer = new ObjectArraySerializer(cls); } else if ((cls.getModifiers() & ENUM) != 0) { if (cls.getSuperclass() != Enum.class) { serializer = get(cls.getSuperclass()); classMap.put(cls, serializer); return serializer; } serializer = new EnumSerializer(cls); } else if (Externalizable.class.isAssignableFrom(cls)) { serializer = new ExternalizableSerializer(cls); } else if (Collection.class.isAssignableFrom(cls) && !hasOptions(cls, FIELD_SERIALIZATION)) { serializer = new CollectionSerializer(cls); } else if (Map.class.isAssignableFrom(cls) && !hasOptions(cls, FIELD_SERIALIZATION)) { serializer = new MapSerializer(cls); } else if (Serializable.class.isAssignableFrom(cls)) { serializer = new GeneratedSerializer(cls); } else { serializer = new InvalidSerializer(cls); } serializer.generateUid(); provideSerializer(serializer); if (cls.isAnonymousClass()) { log.warn("Trying to serialize anonymous class: " + cls.getName()); anonymousClasses.incrementAndGet(); } Renamed renamed = cls.getAnnotation(Renamed.class); if (renamed != null) { renamedClasses.put(renamed.from(), cls); } } return serializer; } }