package net.unicon.cas.addons.ticket.registry;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import org.jasig.cas.ticket.ServiceTicket;
import org.jasig.cas.ticket.Ticket;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
/**
* Hazelcast-based implementation of a distributed <code>TicketRegistry</code>
* <p/>
* This implementation just wraps the Hazelcast's <code>IMap</code> which is an extension of the
* standard Java's <code>ConcurrentMap</code>. The heavy lifting of distributed data partitioning,
* network cluster discovery and join, data replication, etc. is done by Hazelcast's Map implementation.
* <p/>
* The logic for tgt and st timeout settings and dynamically determining the ticket type is borrowed
* from CAS' <code>MemCacheTicketRegistry</code>
*
* @author Dmitriy Kopylenko
* @author Unicon, inc.
* @since 1.9
*/
public class HazelcastTicketRegistry extends AbstractDistributedTicketRegistry {
private final IMap<String, Ticket> ticketsMap;
private static final Logger logger = LoggerFactory.getLogger(HazelcastTicketRegistry.class);
private final long serviceTicketTimeoutInSeconds;
private final long ticketGrantingTicketTimeoutInSeconds;
/**
* @param hz an instance of <code>HazelcastInstance</code> configured on each node
* @param ticketGrantingTicketTimeoutInSeconds for TGT Hazelcast Map entries TTL
* @param serviceTicketTimeoutInSeconds for ST Hazelcast Map entries TTL
* from which it creates an instance of a cluster-aware Map - a main data structure
* where tickets are stored and seamlessly replicated across nodes in the cluster by Hazelcast.
*/
public HazelcastTicketRegistry(final HazelcastInstance hz, long ticketGrantingTicketTimeoutInSeconds, long serviceTicketTimeoutInSeconds) {
logger.info("Constructing TicketRegistry from HazelcastInstance: {}", hz);
logger.info("TicketGrantingTicket timeout is used for Hazelcast TGT entries (in seconds): [{}]", ticketGrantingTicketTimeoutInSeconds);
logger.info("ServiceTicket timeout is used for Hazelcast ST entries (in seconds): [{}]", serviceTicketTimeoutInSeconds);
this.ticketsMap = hz.getMap("tickets");
this.ticketGrantingTicketTimeoutInSeconds = ticketGrantingTicketTimeoutInSeconds;
this.serviceTicketTimeoutInSeconds = serviceTicketTimeoutInSeconds;
}
@Override
protected void updateTicket(Ticket ticket) {
addTicket(ticket);
}
@Override
public void addTicket(Ticket ticket) {
final long ticketTimeout = getTimeout(ticket);
logger.debug("Adding Ticket[{}] to the Hazelcast IMap with a TTL of [{}] seconds", ticket.getId(), ticketTimeout);
this.ticketsMap.set(ticket.getId(), ticket, getTimeout(ticket), TimeUnit.SECONDS);
}
@Override
public Ticket getTicket(String ticketId) {
final Ticket t = this.ticketsMap.get(ticketId);
logger.debug("Returning Ticket[{}] from the Hazelcast IMap", t == null ? "null" : t.getId());
return t == null ? null : getProxiedTicketInstance(t);
}
@Override
public boolean deleteTicket(String ticketId) {
logger.debug("Removing Ticket[{}] from the Hazelcast IMap", ticketId);
return this.ticketsMap.remove(ticketId) != null;
}
@Override
public Collection<Ticket> getTickets() {
return this.ticketsMap.values();
}
@Override
protected boolean needsCallback() {
return false;
}
private long getTimeout(final Ticket t) {
if (t instanceof TicketGrantingTicket) {
return this.ticketGrantingTicketTimeoutInSeconds;
}
else if (t instanceof ServiceTicket) {
return this.serviceTicketTimeoutInSeconds;
}
throw new IllegalArgumentException("Invalid ticket type");
}
}