package org.apereo.cas.ticket.registry; import org.apereo.cas.ticket.Ticket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import javax.validation.constraints.NotNull; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Key-value ticket registry implementation that stores tickets in redis keyed on the ticket ID. * * @author serv * @since 5.1.0 */ public class RedisTicketRegistry extends AbstractTicketRegistry { private static final Logger LOGGER = LoggerFactory.getLogger(RedisTicketRegistry.class); private static final String CAS_TICKET_PREFIX = "CAS_TICKET:"; private static final String NO_REDIS_CLIENT_IS_DEFINED = "No redis client is defined."; @NotNull private final TicketRedisTemplate client; public RedisTicketRegistry(final TicketRedisTemplate client) { this.client = client; } @Override public long deleteAll() { final Set<String> redisKeys = this.client.keys(getPatternTicketRedisKey()); final int size = redisKeys.size(); this.client.delete(redisKeys); return size; } @Override public boolean deleteSingleTicket(final String ticketId) { Assert.notNull(this.client, NO_REDIS_CLIENT_IS_DEFINED); try { final String redisKey = getTicketRedisKey(ticketId); this.client.delete(redisKey); return true; } catch (final Exception e) { LOGGER.error("Ticket not found or is already removed. Failed deleting [{}]", ticketId, e); } return false; } @Override public void addTicket(final Ticket ticket) { Assert.notNull(this.client, NO_REDIS_CLIENT_IS_DEFINED); try { LOGGER.debug("Adding ticket [{}]", ticket); final String redisKey = RedisTicketRegistry.getTicketRedisKey(ticket.getId()); // Encode first, then add final Ticket encodeTicket = this.encodeTicket(ticket); this.client.boundValueOps(redisKey) .set(encodeTicket, getTimeout(ticket), TimeUnit.SECONDS); } catch (final Exception e) { LOGGER.error("Failed to add [{}]", ticket); } } @Override public Ticket getTicket(final String ticketId) { Assert.notNull(this.client, NO_REDIS_CLIENT_IS_DEFINED); try { final String redisKey = RedisTicketRegistry.getTicketRedisKey(ticketId); final Ticket t = this.client.boundValueOps(redisKey).get(); if (t != null) { //Decoding add first return decodeTicket(t); } } catch (final Exception e) { LOGGER.error("Failed fetching [{}] ", ticketId, e); } return null; } @Override public Collection<Ticket> getTickets() { Assert.notNull(this.client, NO_REDIS_CLIENT_IS_DEFINED); final Set<Ticket> tickets = new HashSet<>(); final Set<String> redisKeys = this.client.keys(RedisTicketRegistry.getPatternTicketRedisKey()); redisKeys.forEach(redisKey -> { final Ticket ticket = this.client.boundValueOps(redisKey).get(); if (ticket == null) { this.client.delete(redisKey); } else { // Decoding add first tickets.add(this.decodeTicket(ticket)); } }); return tickets; } @Override public Ticket updateTicket(final Ticket ticket) { Assert.notNull(this.client, NO_REDIS_CLIENT_IS_DEFINED); try { LOGGER.debug("Updating ticket [{}]", ticket); final Ticket encodeTicket = this.encodeTicket(ticket); final String redisKey = RedisTicketRegistry.getTicketRedisKey(ticket.getId()); this.client.boundValueOps(redisKey).set(encodeTicket, getTimeout(ticket), TimeUnit.SECONDS); return encodeTicket; } catch (final Exception e) { LOGGER.error("Failed to update [{}]", ticket); } return null; } /** * If not time out value is specified, expire the ticket immediately. * * @param ticket the ticket * @return timeout */ private static int getTimeout(final Ticket ticket) { final int ttl = ticket.getExpirationPolicy().getTimeToLive().intValue(); if (ttl == 0) { return 1; } return ttl; } // Add a prefix as the key of redis private static String getTicketRedisKey(final String ticketId) { return CAS_TICKET_PREFIX + ticketId; } // pattern all ticket redisKey private static String getPatternTicketRedisKey() { return CAS_TICKET_PREFIX + "*"; } }