/* * Copyright 2010 Martin Grotzke * * 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 de.javakaffee.web.msm.serializer.kryo; import com.esotericsoftware.kryo.*; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.pool.KryoFactory; import com.esotericsoftware.kryo.pool.KryoPool; import de.javakaffee.kryoserializers.*; import de.javakaffee.web.msm.MemcachedBackupSession; import de.javakaffee.web.msm.SessionAttributesTranscoder; import de.javakaffee.web.msm.TranscoderDeserializationException; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.objenesis.strategy.StdInstantiatorStrategy; import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * A {@link SessionAttributesTranscoder} that uses {@link Kryo} for serialization. * * @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a> */ public class KryoTranscoder implements SessionAttributesTranscoder { private static final Log LOG = LogFactory.getLog( KryoTranscoder.class ); public static final int DEFAULT_INITIAL_BUFFER_SIZE = 100 * 1024; public static final int DEFAULT_MAX_BUFFER_SIZE = 2000 * 1024; public static final String DEFAULT_SERIALIZER_FACTORY_CLASS = DefaultFieldSerializerFactory.class.getName(); private final KryoPool _kryoPool; private final int _initialBufferSize; private final int _maxBufferSize; private final KryoDefaultSerializerFactory _defaultSerializerFactory; public KryoTranscoder() { this( null, null, false ); } public KryoTranscoder( final ClassLoader classLoader, final String[] customConverterClassNames, final boolean copyCollectionsForSerialization ) { this( classLoader, customConverterClassNames, copyCollectionsForSerialization, DEFAULT_INITIAL_BUFFER_SIZE, DEFAULT_MAX_BUFFER_SIZE, DEFAULT_SERIALIZER_FACTORY_CLASS ); } public KryoTranscoder( final ClassLoader classLoader, final String[] customConverterClassNames, final boolean copyCollectionsForSerialization, final int initialBufferSize, final int maxBufferSize, final String defaultSerializerFactoryClass ) { LOG.info( "Starting with initialBufferSize " + initialBufferSize + ", maxBufferSize " + maxBufferSize + " and defaultSerializerFactory " + defaultSerializerFactoryClass ); final KryoFactory kryoFactory = createKryoFactory(classLoader, customConverterClassNames, copyCollectionsForSerialization); _kryoPool = new KryoPool.Builder(kryoFactory).softReferences().build(); _initialBufferSize = initialBufferSize; _maxBufferSize = maxBufferSize; _defaultSerializerFactory = loadDefaultSerializerFactory( classLoader, defaultSerializerFactoryClass ); } protected KryoDefaultSerializerFactory loadDefaultSerializerFactory( final ClassLoader classLoader, final String defaultSerializerFactoryClass ) { try { final ClassLoader loader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader(); final Class<?> clazz = Class.forName( defaultSerializerFactoryClass, true, loader ); return (KryoDefaultSerializerFactory) clazz.newInstance(); } catch ( final Exception e ) { throw new RuntimeException("Could not load default serializer factory: " + defaultSerializerFactoryClass, e ); } } private KryoFactory createKryoFactory(final ClassLoader classLoader, final String[] customConverterClassNames, final boolean copyCollectionsForSerialization) { return new KryoFactory() { @Override public Kryo create() { KryoBuilder kryoBuilder = new KryoBuilder() { @Override protected Kryo createKryo(ClassResolver classResolver, ReferenceResolver referenceResolver, StreamFactory streamFactory) { return KryoTranscoder.this.createKryo(classResolver, referenceResolver, streamFactory, classLoader, customConverterClassNames, copyCollectionsForSerialization); } }.withInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); final List<KryoBuilderConfiguration> builderConfigs = load(KryoBuilderConfiguration.class, customConverterClassNames, classLoader); for(KryoBuilderConfiguration config : builderConfigs) { kryoBuilder = config.configure(kryoBuilder); } Kryo kryo = kryoBuilder.build(); kryo.setDefaultSerializer(new KryoDefaultSerializerFactory.SerializerFactoryAdapter(_defaultSerializerFactory)); if ( classLoader != null ) { kryo.setClassLoader( classLoader ); } // com.esotericsoftware.minlog.Log.TRACE = true; kryo.setRegistrationRequired(false); kryo.register( Arrays.asList( "" ).getClass(), new ArraysAsListSerializer() ); kryo.register(InvocationHandler.class, new JdkProxySerializer()); UnmodifiableCollectionsSerializer.registerSerializers(kryo); SynchronizedCollectionsSerializer.registerSerializers(kryo); kryo.addDefaultSerializer(EnumMap.class, EnumMapSerializer.class); SubListSerializers.addDefaultSerializers(kryo); final List<KryoCustomization> customizations = load(KryoCustomization.class, customConverterClassNames, classLoader, kryo); if ( customizations != null ) { for( final KryoCustomization customization : customizations ) { try { LOG.info( "Executing KryoCustomization " + customization.getClass().getName() ); customization.customize( kryo ); } catch( final Throwable e ) { LOG.error( "Could not execute customization " + customization, e ); } } } return kryo; } }; } protected Kryo createKryo(final ClassResolver classResolver, final ReferenceResolver referenceResolver, final StreamFactory streamFactory, final ClassLoader classLoader, final String[] customConverterClassNames, final boolean copyCollectionsForSerialization) { return new Kryo(classResolver, referenceResolver, streamFactory) { private final List<SerializerFactory> serializerFactories = load(SerializerFactory.class, customConverterClassNames, classLoader, this); @Override @SuppressWarnings( { "rawtypes" } ) public Serializer<?> getDefaultSerializer(final Class clazz) { final Serializer<?> customSerializer = loadCustomSerializer( clazz, serializerFactories ); if ( customSerializer != null ) { return customSerializer; } if ( copyCollectionsForSerialization ) { // could also be installed via addDefaultSerializer final Serializer<?> copyCollectionSerializer = loadCopyCollectionSerializer( clazz ); if ( copyCollectionSerializer != null ) { return copyCollectionSerializer; } } return super.getDefaultSerializer( clazz ); } private Serializer<?> loadCustomSerializer(final Class<?> clazz, List<SerializerFactory> serializerFactories) { if ( serializerFactories != null ) { for (SerializerFactory serializerFactory : serializerFactories) { final Serializer<?> serializer = serializerFactory.newSerializer(clazz); if (serializer != null) { if (LOG.isDebugEnabled()) { LOG.debug("Loading custom serializer " + serializer.getClass().getName() + " for class " + clazz); } return serializer; } } } return null; } }; } private Serializer<?> loadCopyCollectionSerializer( final Class<?> clazz ) { if ( Collection.class.isAssignableFrom( clazz ) ) { if ( LOG.isDebugEnabled() ) { LOG.debug( "Loading CopyForIterateCollectionSerializer for class " + clazz ); } return new CopyForIterateCollectionSerializer(); } if ( Map.class.isAssignableFrom( clazz ) ) { if ( LOG.isDebugEnabled() ) { LOG.debug( "Loading CopyForIterateMapSerializer for class " + clazz ); } return new CopyForIterateMapSerializer(); } return null; } /** * {@inheritDoc} */ @SuppressWarnings( "unchecked" ) @Override public ConcurrentMap<String, Object> deserializeAttributes(final byte[] data ) { final Kryo kryo = _kryoPool.borrow(); Input in = null; try { in = kryo.getStreamFactory().getInput(data); return kryo.readObject(in, ConcurrentHashMap.class); } catch ( final RuntimeException e ) { throw new TranscoderDeserializationException( e ); } finally { closeSilently(in); kryo.reset(); // to be safe _kryoPool.release(kryo); } } /** * {@inheritDoc} */ @Override public byte[] serializeAttributes( final MemcachedBackupSession session, final ConcurrentMap<String, Object> attributes ) { final Kryo kryo = _kryoPool.borrow(); Output out = null; try { /** * Creates an ObjectStream with an initial buffer size of 50KB and a maximum size of 1000KB. */ out = kryo.getStreamFactory().getOutput(_initialBufferSize, _maxBufferSize); kryo.writeObject(out, attributes); return out.toBytes(); } catch ( final RuntimeException e ) { throw new TranscoderDeserializationException( e ); } finally { closeSilently(out); kryo.reset(); // to be safe _kryoPool.release(kryo); } } public KryoPool getKryoPool() { return _kryoPool; } private void closeSilently( Closeable closeable ) { if ( closeable != null ) { try { closeable.close(); } catch ( final IOException f ) { // fail silently } } } private <T> List<T> load( Class<T> type, final String[] customConverterClassNames, final ClassLoader classLoader) { return load(type, customConverterClassNames, classLoader, null); } private <T> List<T> load( Class<T> type, final String[] customConverterClassNames, final ClassLoader classLoader, final Kryo kryo) { if (customConverterClassNames == null || customConverterClassNames.length == 0 ) { return Collections.emptyList(); } final List<T> result = new ArrayList<T>(); final ClassLoader loader = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader(); for (final String element : customConverterClassNames) { try { final Class<?> clazz = Class.forName( element, true, loader ); if ( type.isAssignableFrom( clazz ) ) { LOG.info("Loading " + type.getSimpleName() + " " + element); final T item = createInstance(clazz.asSubclass(type), kryo); result.add( item ); } } catch (final Exception e) { LOG.error("Could not instantiate " + element + ", omitting this "+ type.getSimpleName() +".", e); throw new RuntimeException("Could not load "+ type.getSimpleName() +" " + element, e); } } return result; } private static <T> T createInstance( final Class<? extends T> clazz, final Kryo kryo ) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { try { final Constructor<? extends T> constructor = clazz.getConstructor( Kryo.class ); return constructor.newInstance( kryo ); } catch ( final NoSuchMethodException nsme ) { final Constructor<? extends T> constructor = clazz.getConstructor(); return constructor.newInstance(); } } }