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

org.fcrepo.server.security.xacml.pep.ResponseCacheImpl Maven / Gradle / Ivy

The newest version!
/*
 * File: ResponseCache.java
 *
 * Copyright 2007 Macquarie E-Learning Centre Of Excellence
 *
 * Licensed 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.fcrepo.server.security.xacml.pep;

import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.fcrepo.server.security.Attribute;
import org.fcrepo.server.security.RequestCtx;
import org.fcrepo.server.security.xacml.MelcoeXacmlException;
import org.fcrepo.server.security.xacml.util.AttributeComparator;
import org.fcrepo.server.security.xacml.util.ContextUtil;
import org.fcrepo.server.security.xacml.util.SubjectComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jboss.security.xacml.sunxacml.ctx.ResponseCtx;
import org.jboss.security.xacml.sunxacml.ctx.Subject;

/**
 * @author [email protected]
 */
public class ResponseCacheImpl
        implements ResponseCache {

    private static final Logger logger =
            LoggerFactory.getLogger(ResponseCacheImpl.class);

    private final ContextUtil m_contextUtil;

    private static final int DEFAULT_CACHE_SIZE = 1000;

    private static final long DEFAULT_TTL = 10 * 60 * 1000; // 10 minutes

    private static final Attribute[] ATTRIBUTE_TYPE = new Attribute[0];

    private static final AttributeComparator ATTRIBUTE_COMPARATOR = new AttributeComparator();

    private static final Subject[] SUBJECT_TYPE = new Subject[0];

    private static final SubjectComparator SUBJECT_COMPARATOR = new SubjectComparator();

    private final int CACHE_SIZE;

    private long TTL;

    private Map requestCache = null;

    private Map requestCacheTimeTracker = null;

    private List requestCacheUsageTracker = null;

    private MessageDigest digest = null;

    /**
     * The default constructor that initialises the cache with default values.
     *
     * @throws PEPException
     */
    public ResponseCacheImpl(ContextUtil contextUtil)
            throws PEPException {
        this(contextUtil, new Integer(DEFAULT_CACHE_SIZE), new Long(DEFAULT_TTL));
    }

    /**
     * Constructor that initialises the cache with the size and time to live
     * values.
     *
     * @param size
     *        size of the cache
     * @param ttl
     *        maximum time for a cache item to be valid in milliseconds
     * @throws PEPException
     */
    public ResponseCacheImpl(ContextUtil contextUtil, Integer size, Long ttl)
            throws PEPException {

        m_contextUtil = contextUtil;

        TTL = ttl.longValue();

        CACHE_SIZE = size.intValue();

        String noCache = System.getenv("PEP_NOCACHE");
        String noCacheProp = System.getProperty("fedora.fesl.pep_nocache");

        // if system property is set, use that
        if (noCacheProp != null && noCacheProp.toLowerCase().startsWith("t")) {
            TTL = 0;
        } else {
            // if system property is not set ..
            if (noCacheProp == null || noCacheProp.length() == 0) {
                // use env variable if set
                if (noCache != null && noCache.toLowerCase().startsWith("t")) {
                    TTL = 0;
                }
            }
        }

        // Note - HashMap, ArrayList are not thread-safe
        requestCache = new HashMap(CACHE_SIZE);
        requestCacheTimeTracker = new HashMap(CACHE_SIZE);
        requestCacheUsageTracker = new ArrayList(CACHE_SIZE);

        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new PEPException("Could not initialize the ResponseCache", e);
        }
    }

    @Override
    public void setTTL(long ttl) {
        TTL = ttl;
    }

    /*
     * (non-Javadoc)
     * @see org.fcrepo.server.security.xacml.pep.ResponseCache#addCacheItem(java.lang.String,
     * java.lang.String)
     */
    @Override
    public void addCacheItem(String request, ResponseCtx response) {
        String hash = null;

        try {
            hash = makeHash(request);

            // thread-safety on cache operations
            synchronized (requestCache) {

                // if we have a maxxed cache, remove least used item
                if (requestCache.size() >= CACHE_SIZE) {
                    String key = requestCacheUsageTracker.remove(0);
                    requestCache.remove(key);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Purging cache element");
                    }
                }

                requestCache.put(hash, response);
                requestCacheUsageTracker.add(hash);
                requestCacheTimeTracker.put(hash, new Long(System
                                                           .currentTimeMillis()));
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Adding Cache Item (" + requestCache.size() + "/"
                             + requestCacheUsageTracker.size() + "/"
                             + requestCacheTimeTracker.size() + "): " + hash);
            }
        } catch (Exception e) {
            logger.warn("Error adding cache item: " + e.getMessage(), e);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.fcrepo.server.security.xacml.pep.ResponseCache#getCacheItem(java.lang.String)
     */
    @Override
    public ResponseCtx getCacheItem(String request) {
        String hash = null;
        ResponseCtx response = null;

        try {
            hash = makeHash(request);

            // thread-safety on cache operations
            synchronized (requestCache) {

                if (logger.isDebugEnabled()) {
                    logger.debug("Getting Cache Item (" + requestCache.size() + "/"
                                 + requestCacheUsageTracker.size() + "/"
                                 + requestCacheTimeTracker.size() + "): " + hash);
                }

                response = requestCache.get(hash);

                if (response == null) {
                    return null;
                }

                // if this item is older than CACHE_ITEM_TTL then we can't use it
                long usedLast =
                    System.currentTimeMillis()
                    - requestCacheTimeTracker.get(hash).longValue();
                if (usedLast > TTL) {
                    requestCache.remove(hash);
                    requestCacheUsageTracker.remove(hash);
                    requestCacheTimeTracker.remove(hash);

                    if (logger.isDebugEnabled()) {
                        logger.debug("CACHE_ITEM_TTL exceeded: " + hash);
                    }

                    return null;
                }

                // we just used this item, move it to the end of the list (items at
                // beginning get removed...)
                requestCacheUsageTracker.add(requestCacheUsageTracker
                                             .remove(requestCacheUsageTracker.indexOf(hash)));
            }
        } catch (Exception e) {
            logger.warn("Error getting cache item: " + e.getMessage(), e);
            response = null;
        }

        return response;
    }

    /*
     * (non-Javadoc)
     * @see org.fcrepo.server.security.xacml.pep.ResponseCache#invalidate()
     */
    @Override
    public void invalidate() {
        // thread-safety on cache operations
        synchronized (requestCache) {
            requestCache = new HashMap(CACHE_SIZE);
            requestCacheTimeTracker = new HashMap(CACHE_SIZE);
            requestCacheUsageTracker = new ArrayList(CACHE_SIZE);
        }
    }

    /**
     * Given a request, this method generates a hash.
     *
     * @param request
     *        the request to hash
     * @return the hash
     * @throws CacheException
     */
    private String makeHash(String request) throws CacheException {
        RequestCtx reqCtx = null;
        try {
            reqCtx = m_contextUtil.makeRequestCtx(request);
        } catch (MelcoeXacmlException pe) {
            throw new CacheException("Error converting request", pe);
        }
        byte[] hash = null;
        // ensure thread safety, don't want concurrent invocations of this method all modifying digest at once
        // (alternative is to construct a new digest for each(
        synchronized(digest) {
            digest.reset();

            hashSubjectList(reqCtx.getSubjectsAsList(), digest);

            hashAttributeList(reqCtx.getResourceAsList(), digest);

            hashAttributeList(reqCtx.getActionAsList(), digest);

            hashAttributeList(reqCtx.getEnvironmentAttributesAsList(), digest);

            hash = digest.digest();
        }

        return byte2hex(hash);
    }

    @SuppressWarnings("unchecked")
    private static void hashSubjectList(List subjList, MessageDigest digest) {
        Subject[] subjs = subjList.toArray(SUBJECT_TYPE);
        Arrays.sort(subjs, SUBJECT_COMPARATOR);
        for (Subject s:subjs) {
            hashAttributeList(s.getAttributesAsList(), digest);
        }
    }

    private static void hashAttributeList(List attList, MessageDigest digest) {
        Attribute[] atts = attList.toArray(ATTRIBUTE_TYPE);
        Arrays.sort(atts, ATTRIBUTE_COMPARATOR);
        for (Attribute a:atts) {
            hashAttribute(a, digest);
        }
    }
    /**
     * Utility function to add an attribute to the hash digest.
     *
     * @param a
     *        the attribute to hash
     */
    private static void hashAttribute(Attribute a, MessageDigest dig) {
        dig.update(a.getId().toString().getBytes());
        dig.update(a.getType().toString().getBytes());
        dig.update(a.getValue().encode().getBytes());
        if (a.getIssuer() != null) {
            dig.update(a.getIssuer().getBytes());
        }
        if (a.getIssueInstant() != null) {
            dig.update(a.getIssueInstant().encode().getBytes());
        }
    }

    /**
     * Converts a hash into its hexadecimal string representation.
     *
     * @param bytes
     *        the byte array to convert
     * @return the hexadecimal string representation
     */
    private String byte2hex(byte[] bytes) {
        char[] hexChars =
                {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
                        'c', 'd', 'e', 'f'};

        StringBuffer sb = new StringBuffer();
        for (byte b : bytes) {
            sb.append(hexChars[b >> 4 & 0xf]);
            sb.append(hexChars[b & 0xf]);
        }

        return new String(sb);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy