package org.apereo.cas.ticket.registry;
import net.spy.memcached.MemcachedClientIF;
import org.apereo.cas.ticket.Ticket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collection;
/**
* Key-value ticket registry implementation that stores tickets in memcached keyed on the ticket ID.
*
* @author Scott Battaglia
* @author Marvin S. Addison
* @since 3.3
*/
public class MemCacheTicketRegistry extends AbstractTicketRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(MemCacheTicketRegistry.class);
private static final String NO_MEMCACHED_CLIENT_IS_DEFINED = "No memcached client is defined.";
/**
* Memcached client.
*/
private final MemcachedClientIF client;
/**
* Creates a new instance using the given memcached client instance, which is presumably configured via
* {@code net.spy.memcached.spring.MemcachedClientFactoryBean}.
*
* @param client Memcached client.
*/
public MemCacheTicketRegistry(final MemcachedClientIF client) {
this.client = client;
}
@Override
public Ticket updateTicket(final Ticket ticketToUpdate) {
Assert.notNull(this.client, NO_MEMCACHED_CLIENT_IS_DEFINED);
final Ticket ticket = encodeTicket(ticketToUpdate);
LOGGER.debug("Updating ticket [{}]", ticket);
try {
if (!this.client.replace(ticket.getId(), getTimeout(ticketToUpdate), ticket).get()) {
LOGGER.error("Failed to update [{}]", ticket);
return null;
}
} catch (final InterruptedException e) {
LOGGER.warn("Interrupted while waiting for response to async replace operation for ticket [{}]. "
+ "Cannot determine whether update was successful.", ticket);
} catch (final Exception e) {
LOGGER.error("Failed updating [{}]", ticket, e);
}
return ticket;
}
@Override
public void addTicket(final Ticket ticketToAdd) {
Assert.notNull(this.client, NO_MEMCACHED_CLIENT_IS_DEFINED);
try {
final Ticket ticket = encodeTicket(ticketToAdd);
LOGGER.debug("Adding ticket [{}]", ticket);
final int timeout = getTimeout(ticketToAdd);
if (!this.client.add(ticket.getId(), getTimeout(ticketToAdd), ticket).get()) {
LOGGER.error("Failed to add [{}] without timeout [{}]", ticketToAdd, timeout);
}
// Sanity check to ensure ticket can retrieved
if (this.client.get(ticket.getId()) == null) {
LOGGER.warn("Ticket [{}] was added to memcached with timeout [{}], yet it cannot be retrieved. "
+ "Ticket expiration policy may be too aggressive ?", ticketToAdd, timeout);
}
} catch (final InterruptedException e) {
LOGGER.warn("Interrupted while waiting for response to async add operation for ticket [{}]."
+ "Cannot determine whether add was successful.", ticketToAdd);
} catch (final Exception e) {
LOGGER.error("Failed adding [{}]", ticketToAdd, e);
}
}
@Override
public long deleteAll() {
LOGGER.debug("deleteAll() isn't supported. Returning empty list");
return 0;
}
@Override
public boolean deleteSingleTicket(final String ticketId) {
Assert.notNull(this.client, NO_MEMCACHED_CLIENT_IS_DEFINED);
try {
if (this.client.delete(ticketId).get()) {
LOGGER.debug("Removed ticket [{}] from the cache", ticketId);
} else {
LOGGER.info("Ticket [{}] not found or is already removed.", ticketId);
}
} catch (final Exception e) {
LOGGER.error("Ticket not found or is already removed. Failed deleting [{}]", ticketId, e);
}
return true;
}
@Override
public Ticket getTicket(final String ticketIdToGet) {
Assert.notNull(this.client, NO_MEMCACHED_CLIENT_IS_DEFINED);
final String ticketId = encodeTicketId(ticketIdToGet);
try {
final Ticket t = (Ticket) this.client.get(ticketId);
if (t != null) {
return decodeTicket(t);
}
} catch (final Exception e) {
LOGGER.error("Failed fetching [{}] ", ticketId, e);
}
return null;
}
@Override
public Collection<Ticket> getTickets() {
LOGGER.debug("getTickets() isn't supported. Returning empty list");
return new ArrayList<>();
}
/**
* Destroy the client and shut down.
*/
@PreDestroy
public void destroy() {
if (this.client == null) {
return;
}
this.client.shutdown();
}
/**
* If not time out value is specified, expire the ticket immediately.
*
* @param ticket the ticket
* @return timeout in milliseconds.
*/
private static int getTimeout(final Ticket ticket) {
final int ttl = ticket.getExpirationPolicy().getTimeToLive().intValue();
if (ttl == 0) {
return 1;
}
return ttl;
}
}