/* * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at the following location: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jasig.cas.ticket.registry; import java.util.Collection; import java.util.HashSet; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; import net.sf.ehcache.config.CacheConfiguration; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.jasig.cas.ticket.ServiceTicket; import org.jasig.cas.ticket.Ticket; import org.jasig.cas.ticket.TicketGrantingTicket; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.style.ToStringCreator; /** * <p> * <a href="http://ehcache.org/">Ehcache</a> based distributed ticket registry. * </p> * <p> * Use distinct caches for ticket granting tickets (TGT) and service tickets (ST) for: * <ul> * <li>Tuning : use cache level time to live with different values for TGT an ST.</li> * <li>Monitoring : follow separately the number of TGT and ST.</li> * </ul> * </p> * * @author <a href="mailto:cleclerc@xebia.fr">Cyrille Le Clerc</a> * @author Adam Rybicki * @author Andrew Tillinghast */ public final class EhCacheTicketRegistry extends AbstractDistributedTicketRegistry implements InitializingBean { private Cache serviceTicketsCache = null; private Cache ticketGrantingTicketsCache = null; /** @see #setSupportRegistryState(boolean)*/ private boolean supportRegistryState = true; public EhCacheTicketRegistry() { } public EhCacheTicketRegistry(final Cache serviceTicketsCache, final Cache ticketGrantingTicketsCache) { super(); setServiceTicketsCache(serviceTicketsCache); setTicketGrantingTicketsCache(ticketGrantingTicketsCache); } public EhCacheTicketRegistry(final Cache serviceTicketsCache, final Cache ticketGrantingTicketsCache, final boolean supportRegistryState) { this(serviceTicketsCache, ticketGrantingTicketsCache); setSupportRegistryState(supportRegistryState); } @Override public void addTicket(final Ticket ticket) { final Element element = new Element(ticket.getId(), ticket); if (ticket instanceof ServiceTicket) { logger.debug("Adding service ticket {} to the cache", ticket.getId(), this.serviceTicketsCache.getName()); this.serviceTicketsCache.put(element); } else if (ticket instanceof TicketGrantingTicket) { logger.debug("Adding ticket granting ticket {} to the cache {}", ticket.getId(), this.ticketGrantingTicketsCache.getName()); this.ticketGrantingTicketsCache.put(element); } else { throw new IllegalArgumentException("Invalid ticket type " + ticket); } } @Override public boolean deleteTicket(final String ticketId) { if (StringUtils.isBlank(ticketId)) { return false; } return this.serviceTicketsCache.remove(ticketId) || this.ticketGrantingTicketsCache.remove(ticketId); } @Override public Ticket getTicket(final String ticketId) { if (ticketId == null) { return null; } Element element = this.serviceTicketsCache.get(ticketId); if (element == null) { element = this.ticketGrantingTicketsCache.get(ticketId); } return element == null ? null : getProxiedTicketInstance((Ticket) element.getObjectValue()); } @Override public Collection<Ticket> getTickets() { final Collection<Element> serviceTickets = this.serviceTicketsCache.getAll( this.serviceTicketsCache.getKeysWithExpiryCheck()).values(); final Collection<Element> tgtTicketsTickets = this.ticketGrantingTicketsCache.getAll( this.ticketGrantingTicketsCache.getKeysWithExpiryCheck()).values(); final Collection<Ticket> allTickets = new HashSet<Ticket>(serviceTickets.size() + tgtTicketsTickets.size()); for (final Element ticket : serviceTickets) { allTickets.add((Ticket) ticket.getObjectValue()); } for (final Element ticket : tgtTicketsTickets) { allTickets.add((Ticket) ticket.getObjectValue()); } return allTickets; } public void setServiceTicketsCache(final Cache serviceTicketsCache) { this.serviceTicketsCache = serviceTicketsCache; } public void setTicketGrantingTicketsCache(final Cache ticketGrantingTicketsCache) { this.ticketGrantingTicketsCache = ticketGrantingTicketsCache; } @Override public String toString() { return new ToStringCreator(this).append("ticketGrantingTicketsCache", this.ticketGrantingTicketsCache) .append("serviceTicketsCache", this.serviceTicketsCache).toString(); } @Override protected void updateTicket(final Ticket ticket) { addTicket(ticket); } @Override protected boolean needsCallback() { return false; } /** * Flag to indicate whether this registry instance should participate in reporting its state with * default value set to <code>true</code>. * Based on the <a href="http://ehcache.org/apidocs/net/sf/ehcache/Ehcache.html#getKeysWithExpiryCheck()">EhCache documentation</a>, * determining the number of service tickets and the total session count from the cache can be considered * an expensive operation with the time taken as O(n), where n is the number of elements in the cache. * * <p>Therefore, the flag provides a level of flexibility such that depending on the cache and environment * settings, reporting statistics * can be set to false and disabled.</p> * * @param supportRegistryState true, if the registry is to support registry state * @see #sessionCount() * @see #serviceTicketCount() * @see org.jasig.cas.monitor.SessionMonitor */ public void setSupportRegistryState(final boolean supportRegistryState) { this.supportRegistryState = supportRegistryState; } @Override public void afterPropertiesSet() throws Exception { if (this.serviceTicketsCache == null || this.ticketGrantingTicketsCache == null) { throw new BeanInstantiationException(this.getClass(), "Both serviceTicketsCache and ticketGrantingTicketsCache are required properties."); } if (logger.isDebugEnabled()) { CacheConfiguration config = this.serviceTicketsCache.getCacheConfiguration(); logger.debug("serviceTicketsCache.maxElementsInMemory={}", config.getMaxEntriesLocalHeap()); logger.debug("serviceTicketsCache.maxElementsOnDisk={}", config.getMaxElementsOnDisk()); logger.debug("serviceTicketsCache.isOverflowToDisk={}", config.isOverflowToDisk()); logger.debug("serviceTicketsCache.timeToLive={}", config.getTimeToLiveSeconds()); logger.debug("serviceTicketsCache.timeToIdle={}", config.getTimeToIdleSeconds()); logger.debug("serviceTicketsCache.cacheManager={}", this.serviceTicketsCache.getCacheManager().getName()); config = this.ticketGrantingTicketsCache.getCacheConfiguration(); logger.debug("ticketGrantingTicketsCache.maxElementsInMemory={}", config.getMaxEntriesLocalHeap()); logger.debug("ticketGrantingTicketsCache.maxElementsOnDisk={}", config.getMaxElementsOnDisk()); logger.debug("ticketGrantingTicketsCache.isOverflowToDisk={}", config.isOverflowToDisk()); logger.debug("ticketGrantingTicketsCache.timeToLive={}", config.getTimeToLiveSeconds()); logger.debug("ticketGrantingTicketsCache.timeToIdle={}", config.getTimeToIdleSeconds()); logger.debug("ticketGrantingTicketsCache.cacheManager={}", this.ticketGrantingTicketsCache.getCacheManager() .getName()); } } /** * {@inheritDoc} * @see Cache#getKeysWithExpiryCheck() */ @Override public int sessionCount() { return BooleanUtils.toInteger(this.supportRegistryState, this.ticketGrantingTicketsCache .getKeysWithExpiryCheck().size(), super.sessionCount()); } /** * {@inheritDoc} * @see Cache#getKeysWithExpiryCheck() */ @Override public int serviceTicketCount() { return BooleanUtils.toInteger(this.supportRegistryState, this.serviceTicketsCache.getKeysWithExpiryCheck() .size(), super.serviceTicketCount()); } }