Entries Tagged 'Java' ↓

Reducing Visible Configuration in CAS4

One of the issues we always had with CAS3 was the amount of Spring XML Configuration one was exposed to, whether it needed to be changed or not, and the management of those files.  If we added/removed/changed anything in the available Spring configuration files, upgraders would have to double check it against their configuration files to make sure they didn’t miss anything.

We tried our best to isolate user changes from the software configuration through things like the deployerConfigContext.xml and the Maven overly of a more fine-grained set of Spring XML configuration files.  Despite our best efforts, its still not great.

We’re looking to make progress on this in CAS4.  So far, we’ve identified the following areas where we can make improvement:

  • Items that require no configuration, but just to be “wired” up will have their default configuration files included in the JAR that the class is included in.  This way, for example, if you need the default InMemorySessionStorage, you include the appropriate JAR as a Maven2 dependency.  Within that JAR, we have a Spring XML configuration pre-configured with the appropriate beans, and its just automatically loaded.  We would combine this with autowiring so that other components can find and inject the SessionStorage.  Another example, would be that the InMemorySessionStorage may need a set of SessionFactories.  You could wire them up automatically, or Autowiring could locate them and inject them automatically.  Clearly, there are some limits to this.  Things that will be heavily customized or have specific ordering needs would need to be manually wired.
  • Items that shouldn’t change shouldn’t show up.  The CAS protocol specifies a set of URLs that need to be exposed.  But they are part of the protocol.  Why should they show up in the configuration files only so that we can tell you not to touch them.  For things like this, we’ve come to rely on Annotations wherever possible (i.e. the Spring MVC Annotations)
  • Identifying “reality” extension points, and extensions points that just exist because we can.  We’re taking a closer look (we’ll go into more detail in a different post) at the extensions and collaborators we are exposing just because we can vs. what we’re exposing because we should. Going hand-in-hand with this is relying on sensible defaults for those things that are in-between we should expose them and we’re exposing them just because we can.

We’re hoping that the changes above, and any other changes identified (either on-list or in the comments below) make it easier for people to configure CAS.  As code starts to show up into our Subversion, we’ll go into more detail.

Design Decisions, Part 1: SessionStorage

In CAS3, there was an interface called TicketRegistry.  It had some periphery supporting things such as a TicketRegistryCleaner and Tickets (both ServiceTicket and TicketGrantingTicket).

Some Issues With the Current Design

When CAS3 was created, there was no notion of the fact that you may need different implementations of Tickets (even though we had an interface).  This caused some problems when other TicketRegistry implementations were needed, as the default implementation became overloaded with JPA annotations, and  was inefficiently stored by the backing mechanism.  Many of the distributed ticket registries actually returned proxies.

In addition, the initial idea of the TicketRegistryCleaner was that you could define your own algorithms outside of the TicketRegistry to meet your needs.  Unfortunately, the TicketRegistries often know best on how to clean themselves up (i.e. a JDBC version might just be able to call DELETE FROM ticketRegistry where expired=true).

The New Design

We’re looking at re-working the TicketRegistry and supporting interfaces. (Disclaimer: Please ignore the possibly poorly worded choices for interfaces, we’re looking into generalizing terms since CAS now supports more than the CAS1&2 protocol).

You’ll see our first pass at a storage for long term sessions below. Long term sessions are similar to TicketGrantingTickets. There isn’t much difference other than the fact that sessions aren’t CAS protocol specific.

/*
 * Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license
 * distributed with this file and available online at
 * http://www.ja-sig.org/products/cas/overview/license/index.html
 */
package org.jasig.cas.server.session;

import org.jasig.cas.server.authentication.Authentication;

/**
 * Represents a storage area for session information.  The backing mechanism can be any mechanism that
 * can store Java objects or a representation of those Java objects, such as in-memory, database, etc.
 * Because the underlying-specifics of the Sessions may be dependent on the backing mechanism, the
 * SessionStorage implementation is responsible for creating sessions.
 * <p>
 * Classes that rely on the SessionStorage should never assume that updates are automatically persisted
 * and should take care to call updateSession after making changes to the underlying session.
 * <p>
 * Implementations of this class should be thread-safe.
 *
 * @author Scott Battaglia
 * @version $Revision$ $Date$
 * @since 4.0.0
 */
public interface SessionStorage {

    /**
     * Constructs a new Session from an existing AuthenticationRequest.
     * <p>
     * Note: In CAS3, these were called TicketGrantingTickets. We've disambiguated from that term because we are
     * supporting more than one protocol going forward.
     *
     * @param authentication the successful authentication that will be used to populate this session.
     * @return the fully constructed session.  This should never return null.
     */
    Session createSession(Authentication authentication);

    /**
     * Destroys an existing session, which means that it erases it from whatever backing storage
     * the implementation is using.
     * <p>
     * Method returns the Session in case the calling class has any last minute need for the Session.
     *
     * @param sessionId the session identifier to look for.
     * @return the Session. May return null if it can't find it.
     */
    Session destroySession(String sessionId);

    /**
     * Locates an existing session based on its existing session identifier.
     *
     * @param sessionId the id of the session to look for.
     * @return the Session, if found.  Null, otherwise.
     */
    Session findSessionBySessionId(String sessionId);

    /**
     * Updates the existing session in the backing-storage mechanism.  Depending on the implementation
     * this may or may not actually do anything.  One could imagine an in-memory version not needing to
     * do anything explicit to save a session back as all operations take place in-memory.
     *
     * @param session the session to update in the backing data-store.
     * @return the session that was updated.
     */
    Session updateSession(Session session);

    /**
     * Locate a session based on the access request identifier.
     * <p>
     * Note: In CAS3, ServiceTickets take the place of Access.
     *
     * @param accessId the identifier for which to retrieve the service by.
     *
     * @return the session associated with the ServiceAccessId, or null if the match can't be found.
     */
    Session findSessionByAccessId(String accessId);

    /**
     * Notifies the SessionStorage object that it should attempt to clean up its internal data-store.
     * <p>
     * Note that while the SessionStorage can delay or bundle multiple requests together, it should never
     * completely ignore the request to prune its own internal data-store.
     * <p>
     * The one exception to the above rule is SessionStorage objects who's internal data-store has its own
     * mechanism for cleaning itself (i.e. memcached's timeout mechanism).  In this particular instance, prune() can
     * be considered a no-op.
     *
     */
    void prune();
}

Some of the design considerations:

  1. We’ve moved the algorithm for cleaning a registry to the actual registry itself.  This way a Jpa or JDBC registry could merely call DELETE FROM… or something like MemCached wouldn’t need to do anything.  This eliminates the need to enable/disable the Quartz scheduler within the WAR file, as the individual prune method may or may not do anything depending on the implementation.
  2. Registries (new term: SessionStorage) are now responsible for instantiating the initial Session.  This allows us to separate out the different implementations of a Session and have the correct one associated with a registry (instead of overloading say the default implementation in CAS3).
  3. We have an explicit method that should be called to notify the registry/sessionstorage that you’ve made changes to the underlying sessions.  One could argue that its up to the SessionStorage to monitor that by returning Sessions that can speak with their SessionStorage.  Therefore, this may change.