package org.apereo.cas.ticket.registry.support.kryo;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.DefaultSerializers;
import de.javakaffee.kryoserializers.CollectionsEmptyListSerializer;
import de.javakaffee.kryoserializers.CollectionsEmptyMapSerializer;
import de.javakaffee.kryoserializers.CollectionsEmptySetSerializer;
import de.javakaffee.kryoserializers.EnumMapSerializer;
import de.javakaffee.kryoserializers.EnumSetSerializer;
import de.javakaffee.kryoserializers.KryoReflectionFactorySupport;
import de.javakaffee.kryoserializers.RegexSerializer;
import de.javakaffee.kryoserializers.URISerializer;
import de.javakaffee.kryoserializers.UUIDSerializer;
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer;
import de.javakaffee.kryoserializers.guava.ImmutableListSerializer;
import de.javakaffee.kryoserializers.guava.ImmutableMapSerializer;
import de.javakaffee.kryoserializers.guava.ImmutableMultimapSerializer;
import de.javakaffee.kryoserializers.guava.ImmutableSetSerializer;
import net.spy.memcached.CachedData;
import net.spy.memcached.transcoders.Transcoder;
import org.apereo.cas.authentication.BasicCredentialMetaData;
import org.apereo.cas.authentication.DefaultHandlerResult;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.principal.SimplePrincipal;
import org.apereo.cas.authentication.principal.SimpleWebApplicationServiceImpl;
import org.apereo.cas.services.RegexRegisteredService;
import org.apereo.cas.ticket.ServiceTicketImpl;
import org.apereo.cas.ticket.TicketGrantingTicketImpl;
import org.apereo.cas.ticket.registry.EncodedTicket;
import org.apereo.cas.ticket.registry.support.kryo.serial.RegisteredServiceSerializer;
import org.apereo.cas.ticket.registry.support.kryo.serial.SimpleWebApplicationServiceSerializer;
import org.apereo.cas.ticket.registry.support.kryo.serial.URLSerializer;
import org.apereo.cas.ticket.registry.support.kryo.serial.ZonedDateTimeTranscoder;
import org.apereo.cas.ticket.support.HardTimeoutExpirationPolicy;
import org.apereo.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy;
import org.apereo.cas.ticket.support.NeverExpiresExpirationPolicy;
import org.apereo.cas.ticket.support.RememberMeDelegatingExpirationPolicy;
import org.apereo.cas.ticket.support.ThrottledUseAndTimeoutExpirationPolicy;
import org.apereo.cas.ticket.support.TicketGrantingTicketExpirationPolicy;
import org.apereo.cas.ticket.support.TimeoutExpirationPolicy;
import org.apereo.cas.authentication.DefaultAuthentication;
import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URL;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
/**
* {@link net.spy.memcached.MemcachedClient} transcoder implementation based on Kryo fast serialization framework
* suited for efficient serialization of tickets.
*
* @author Marvin S. Addison
* @since 3.0.0
*/
@SuppressWarnings("rawtypes")
public class KryoTranscoder implements Transcoder<Object> {
/**
* Kryo serializer.
*/
private final Kryo kryo = new KryoReflectionFactorySupport();
/**
* Map of class to serializer that handles it.
*/
private Map<Class<?>, Serializer> serializerMap;
/**
* Creates a Kryo-based transcoder.
*/
public KryoTranscoder() {
}
/**
* Sets a map of additional types that should be registered with Kryo,
* for example GoogleAccountsService and OpenIdService.
*
* @param map Map of class to the serializer instance that handles it.
*/
public void setSerializerMap(final Map<Class<?>, Serializer> map) {
this.serializerMap = map;
}
/**
* Initialize and register classes with kryo.
*/
@PostConstruct
public void initialize() {
// Register types we know about and do not require external configuration
this.kryo.register(EncodedTicket.class);
this.kryo.register(ArrayList.class);
this.kryo.register(BasicCredentialMetaData.class);
this.kryo.register(Class.class, new DefaultSerializers.ClassSerializer());
this.kryo.register(ZonedDateTime.class, new ZonedDateTimeTranscoder());
this.kryo.register(HardTimeoutExpirationPolicy.class);
this.kryo.register(HashMap.class);
this.kryo.register(LinkedHashMap.class);
this.kryo.register(HashSet.class);
this.kryo.register(DefaultHandlerResult.class);
this.kryo.register(DefaultAuthentication.class);
this.kryo.register(MultiTimeUseOrTimeoutExpirationPolicy.class);
this.kryo.register(NeverExpiresExpirationPolicy.class);
this.kryo.register(RememberMeDelegatingExpirationPolicy.class);
this.kryo.register(ServiceTicketImpl.class);
this.kryo.register(SimpleWebApplicationServiceImpl.class, new SimpleWebApplicationServiceSerializer());
this.kryo.register(ThrottledUseAndTimeoutExpirationPolicy.class);
this.kryo.register(TicketGrantingTicketExpirationPolicy.class);
this.kryo.register(TicketGrantingTicketImpl.class);
this.kryo.register(TimeoutExpirationPolicy.class);
this.kryo.register(UsernamePasswordCredential.class);
this.kryo.register(SimplePrincipal.class);
this.kryo.register(URL.class, new URLSerializer());
this.kryo.register(URI.class, new URISerializer());
this.kryo.register(Pattern.class, new RegexSerializer());
this.kryo.register(UUID.class, new UUIDSerializer());
this.kryo.register(EnumMap.class, new EnumMapSerializer());
this.kryo.register(EnumSet.class, new EnumSetSerializer());
// we add these ones for tests only
this.kryo.register(RegexRegisteredService.class, new RegisteredServiceSerializer());
// from the kryo-serializers library (https://github.com/magro/kryo-serializers)
UnmodifiableCollectionsSerializer.registerSerializers(this.kryo);
ImmutableListSerializer.registerSerializers(this.kryo);
ImmutableSetSerializer.registerSerializers(this.kryo);
ImmutableMapSerializer.registerSerializers(this.kryo);
ImmutableMultimapSerializer.registerSerializers(this.kryo);
this.kryo.register(Collections.EMPTY_LIST.getClass(), new CollectionsEmptyListSerializer());
this.kryo.register(Collections.EMPTY_MAP.getClass(), new CollectionsEmptyMapSerializer());
this.kryo.register(Collections.EMPTY_SET.getClass(), new CollectionsEmptySetSerializer());
// Register other types
if (this.serializerMap != null) {
this.serializerMap.forEach(this.kryo::register);
}
// don't reinit the registered classes after every write or read
this.kryo.setAutoReset(false);
// don't replace objects by references
this.kryo.setReferences(false);
// Catchall for any classes not explicitly registered
this.kryo.setRegistrationRequired(false);
}
/**
* Asynchronous decoding is not supported.
*
* @param d Data to decode.
* @return False.
*/
@Override
public boolean asyncDecode(final CachedData d) {
return false;
}
@Override
public CachedData encode(final Object obj) {
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
try (Output output = new Output(byteStream)) {
this.kryo.writeClassAndObject(output, obj);
output.flush();
final byte[] bytes = byteStream.toByteArray();
return new CachedData(0, bytes, bytes.length);
}
}
@Override
public Object decode(final CachedData d) {
final byte[] bytes = d.getData();
try (Input input = new Input(new ByteArrayInputStream(bytes))) {
final Object obj = this.kryo.readClassAndObject(input);
return obj;
}
}
/**
* Maximum size of encoded data supported by this transcoder.
*
* @return {@code net.spy.memcached.CachedData#MAX_SIZE}.
*/
@Override
public int getMaxSize() {
return CachedData.MAX_SIZE;
}
/**
* Gets the kryo object that provides encoding and decoding services for this instance.
*
* @return Underlying Kryo instance.
*/
public Kryo getKryo() {
return this.kryo;
}
}