All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apereo.cas.ticket.registry.RedisTicketRegistry Maven / Gradle / Ivy

package org.apereo.cas.ticket.registry;

import org.apereo.cas.ticket.Ticket;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;

import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Key-value ticket registry implementation that stores tickets in redis keyed on the ticket ID.
 *
 * @author serv
 * @since 5.1.0
 */
@Slf4j
@RequiredArgsConstructor
public class RedisTicketRegistry extends AbstractTicketRegistry {
    private static final String CAS_TICKET_PREFIX = "CAS_TICKET:";
    private static final long SCAN_COUNT = 100L;

    private final RedisTemplate client;

    /**
     * If not time out value is specified, expire the ticket immediately.
     *
     * @param ticket the ticket
     * @return timeout
     */
    private static Long getTimeout(final Ticket ticket) {
        val ttl = ticket.getExpirationPolicy().getTimeToLive();
        if (ttl > Integer.MAX_VALUE) {
            return (long) Integer.MAX_VALUE;
        } else if (ttl <= 0) {
            return 1L;
        }
        return ttl;
    }

    private static String getTicketRedisKey(final String ticketId) {
        return CAS_TICKET_PREFIX + ticketId;
    }

    private static String getPatternTicketRedisKey() {
        return CAS_TICKET_PREFIX + '*';
    }

    @Override
    public long deleteAll() {
        val redisKeys = this.client.keys(getPatternTicketRedisKey());
        if (redisKeys == null) {
            LOGGER.warn("Unable to locate tickets via redis key");
            return 0;
        }
        val size = redisKeys.size();
        this.client.delete(redisKeys);
        return size;
    }

    @Override
    public boolean deleteSingleTicket(final String ticketId) {
        try {
            val 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) {
        try {
            LOGGER.debug("Adding ticket [{}]", ticket);
            val redisKey = getTicketRedisKey(ticket.getId());
            val encodeTicket = encodeTicket(ticket);
            val timeout = getTimeout(ticket);
            this.client.boundValueOps(redisKey).set(encodeTicket, timeout.longValue(), TimeUnit.SECONDS);
        } catch (final Exception e) {
            LOGGER.error("Failed to add [{}]", ticket, e);
        }
    }

    @Override
    public Ticket getTicket(final String ticketId, final Predicate predicate) {
        try {
            val redisKey = getTicketRedisKey(ticketId);
            val t = this.client.boundValueOps(redisKey).get();
            if (t != null) {
                val result = decodeTicket(t);
                if (predicate.test(result)) {
                    return result;
                }
                LOGGER.debug("The condition enforced by the predicate [{}] cannot successfully accept/test the ticket id [{}]", ticketId,
                        predicate.getClass().getSimpleName());
                return null;
            }
        } catch (final Exception e) {
            LOGGER.error("Failed fetching [{}] ", ticketId, e);
        }
        return null;
    }

    @Override
    public Collection getTickets() {
        try (val ticketsStream = getTicketsStream()) {
            return ticketsStream.collect(Collectors.toSet());
        }
    }

    @Override
    public Stream getTicketsStream() {
        return getKeysStream()
            .map(redisKey -> {
                val ticket = this.client.boundValueOps(redisKey).get();
                if (ticket == null) {
                    this.client.delete(redisKey);
                    return null;
                }
                return ticket;
            })
            .filter(Objects::nonNull)
            .map(this::decodeTicket);

    }

    @Override
    public Ticket updateTicket(final Ticket ticket) {
        try {
            LOGGER.debug("Updating ticket [{}]", ticket);
            val encodeTicket = this.encodeTicket(ticket);
            val redisKey = getTicketRedisKey(ticket.getId());
            LOGGER.debug("Fetched redis key [{}] for ticket [{}]", redisKey, ticket);

            val timeout = getTimeout(ticket);
            this.client.boundValueOps(redisKey).set(encodeTicket, timeout.longValue(), TimeUnit.SECONDS);
            return encodeTicket;
        } catch (final Exception e) {
            LOGGER.error("Failed to update [{}]", ticket, e);
        }
        return null;
    }

    /**
     * Get a stream of all CAS-related keys from Redis DB.
     *
     * @return stream of all CAS-related keys from Redis DB
     */
    private Stream getKeysStream() {
        val cursor = client.getConnectionFactory().getConnection()
                .scan(ScanOptions.scanOptions().match(getPatternTicketRedisKey())
                .count(SCAN_COUNT)
                .build());
        return StreamSupport
            .stream(Spliterators.spliteratorUnknownSize(cursor, Spliterator.ORDERED), false)
            .map(key -> (String) client.getKeySerializer().deserialize(key))
            .collect(Collectors.toSet())
            .stream()
            .onClose(() -> {
                try {
                    cursor.close();
                } catch (final IOException e) {
                    LOGGER.error("Could not close Redis connection", e);
                }
            });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy