org.wicketstuff.shiro.wicket.page.store.SessionPageStore.java Source code

Java tutorial

Introduction

Here is the source code for org.wicketstuff.shiro.wicket.page.store.SessionPageStore.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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
 *
 *      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.wicketstuff.shiro.wicket.page.store;

import java.io.Serializable;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.wicket.Application;
import org.apache.wicket.Page;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.page.IManageablePage;
import org.apache.wicket.pageStore.IPageStore;
import org.apache.wicket.util.lang.Args;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An {@link IPageStore IPageStore}
 * implementation that stores serialized Pages in JSecurity's
 * {@link org.apache.shiro.session.Session Session}. This implementation exists to support
 * applications that use JSecurity's Enterprise Sessions instead of HTTP-only sessions.
 * <p/>
 * In a distributed application, the JSecurity Session data might not reside on the same host that
 * runs the Wicket application. In these cases the default
 * {@link org.apache.wicket.pageStore.DiskDataStore} used by Wicket is
 * not suitable. Instead Page state must be serialized to a mechanism that is 'cluster-friendly'.
 * <p/>
 * JSecurity's enterprise {@code Session}s are cluster-friendly, so storing pages in the
 * {@code Session} is a good choice. This means applications can utilize true generic load balancing
 * and do not need <a
 * href="http://en.wikipedia.org/wiki/Load_balancing_(computing)#Persistence">sticky sessions</a> -
 * the Wicket PageMap can be updated on one host which is then available for any next load-balanced
 * request on another host because the {@code Session} is clustered.
 * 
 * @author Les Hazlewood
 * @author <a href="http://sebthom.de/">Sebastian Thomschke</a>
 */
public class SessionPageStore implements IPageStore {
    private static final Logger LOG = LoggerFactory.getLogger(SessionPageStore.class);
    private static final String PAGE_MAP_SESSION_KEY = SessionPageStore.class.getName()
            + "_PAGE_CACHE_MANAGER_SESSION_KEY";

    protected static final int DEFAULT_MAX_PAGES = -1;

    private final int MAX_PAGE_MAP_SIZE;

    public SessionPageStore() {
        this(DEFAULT_MAX_PAGES);
    }

    public SessionPageStore(final int maxPageMapSize) {
        if (maxPageMapSize < -1) {
            MAX_PAGE_MAP_SIZE = DEFAULT_MAX_PAGES;
            LOG.info("Created SessionPageStore: unlimited number of pages allowed.");
        } else {
            MAX_PAGE_MAP_SIZE = maxPageMapSize;
            LOG.info("Created SessionPageStore: [{}] maximum number of pages allowed.", maxPageMapSize);
        }
    }

    public boolean containsPage(final String sessionId, final int pageId) {
        return getPageCacheManager(sessionId).getPageCache().containsPage(pageId);
    }

    @Override
    public IManageablePage convertToPage(final Object page) {
        if (page == null)
            return null;

        if (page instanceof IManageablePage)
            return (IManageablePage) page;

        if (page instanceof byte[])
            return deserializePage((byte[]) page);

        throw new IllegalArgumentException("Unknown object type " + page.getClass().getName());

    }

    protected IManageablePage deserializePage(final byte data[]) {
        // TODO: test this, Serializer replacing old
        // WicketObjects.byteArrayToObject(data);
        // call
        return (IManageablePage) Application.get().getFrameworkSettings().getSerializer().deserialize(data);
    }

    @Override
    public void destroy() {
        // do nothing - session timeout will cleanup automatically
    }

    public int getMaxPageMapSize() {
        return MAX_PAGE_MAP_SIZE;
    }

    @Override
    public Page getPage(final String sessionId, final int pageId) {
        final SerializedPageWrapper wrapper = getPageCacheManager(sessionId).getPageCache().getPage(pageId);
        final byte[] sPage = wrapper != null ? (byte[]) wrapper.getPage() : null;
        return (Page) (sPage != null ? deserializePage(sPage) : null);
    }

    protected PageCacheManager getPageCacheManager(final String sessionId) {
        final Session session = getSessionForUpdate(sessionId);
        PageCacheManager pcc = (PageCacheManager) session.getAttribute(PAGE_MAP_SESSION_KEY);
        if (pcc == null) {
            pcc = new PageCacheManager(getMaxPageMapSize());
            session.setAttribute(PAGE_MAP_SESSION_KEY, pcc);
        }
        return pcc;
    }

    protected Session getSession(final String sessionId) {
        Session session = null;
        final Subject currentSubject = SecurityUtils.getSubject();
        if (currentSubject != null) {
            session = currentSubject.getSession(false);
            /*
             * guarantee we pulled the same session that Wicket expects us to pull. Because Shiro's
             * Subject acquisition in web apps is based on the incoming request, and so is Wicket's,
             * this should _always_ be the same. If not, something is seriously wrong:
             */
            if (session != null && sessionId != null && !sessionId.equals(session.getId()))
                throw new WicketRuntimeException("The specified Wicket sessionId [" + sessionId
                        + "] is not the same as Shiro's current Subject Session with id [" + session.getId()
                        + "], indicating the Wicket request's session is not the same as Shiro's current Subject Session. "
                        + "The two must always be equal when using the " + getClass().getName()
                        + " implementation. "
                        + "If you're seeing this exception, ensure you have configured Shiro to use Enterprise Sessions and not (the default) HTTP-only Sessions.");
        }
        return session;
    }

    protected Session getSessionForUpdate(final String sessionId) {
        Session session = getSession(sessionId);
        if (session == null)
            session = SecurityUtils.getSubject().getSession();
        return session;
    }

    public void pageAccessed(final String sessionId, final Page page) {
        // nothing to do
    }

    @Override
    public Serializable prepareForSerialization(final String sessionId, final Serializable page) {
        return null;
    }

    @Override
    public void removePage(final String sessionId, final int pageId) {
        if (pageId != -1) {
            LOG.debug("Removing page with id [{}]", pageId);
            getPageCacheManager(sessionId).getPageCache().removePage(pageId);
        }
    }

    @Override
    public Object restoreAfterSerialization(final Serializable serializable) {
        return null;
    }

    protected SerializedPageWrapper serialize(final String sessionId, final IManageablePage page) {
        final byte[] serializedPage = serializePage(sessionId, page);
        return wrap(serializedPage, page.getPageId());
    }

    protected byte[] serializePage(final String sessionId, final IManageablePage page) {
        Args.notNull(sessionId, "sessionId");
        Args.notNull(page, "page");

        // TODO: test this, Serializer replacing old
        // WicketObjects.objectToByteArray(page, applicationName);
        // call
        final byte data[] = Application.get().getFrameworkSettings().getSerializer().serialize(page);
        return data;
    }

    @Override
    public void storePage(final String sessionId, final IManageablePage page) {
        final SerializedPageWrapper wrapper = serialize(sessionId, page);
        getPageCacheManager(sessionId).getPageCache().storePages(wrapper);
        if (LOG.isDebugEnabled())
            LOG.debug("storePage {}", page.toString());
    }

    @Override
    public void unbind(final String sessionId) {
        final Session active = getSession(sessionId);
        if (active != null) {
            final Object existing = active.removeAttribute(PAGE_MAP_SESSION_KEY);
            if (existing != null)
                LOG.debug("Removed PageMap [{}] from the Session (destroying)", existing);
        }
    }

    SerializedPageWrapper wrap(final byte[] serializedPages, final int pageId) {
        final SerializedPageWrapper wrapper = new SerializedPageWrapper(serializedPages, pageId);
        return wrapper;
    }
}