/* * 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.apache.sling.bundleresource.impl; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.osgi.framework.Bundle; /** * The <code>BundleResourceCache</code> implements a simple caching for * resources provided from a bundle. Each {@link BundleResourceProvider} * instance uses an instance of this class to access the bundle resources (or * bundle entries) through the cache. * <p> * The cache on the one hand caches single entries as URLs. The other part of * the cache is for the child entries of a given bundle entry path. This caches * lists of strings (entry path). * <p> * Currently the cache limits are fixed at {@value #CACHE_SIZE} for the entries * cache and at {@value #LIST_CACHE_SIZE} for the child entries cache. */ class BundleResourceCache { /** * The maximum size of the single entry cache (value is 50). */ private static final int CACHE_SIZE = 50; /** * The maximum size of the child entry cache (value is 20). */ private static final int LIST_CACHE_SIZE = 20; /** * Sentinel for the single entry cache representing a missing entry to * prevent looking for non-existing bundle entries multiple times (value is * "file:///not_found"). */ private static final URL NOT_FOUND_URL; /** * Sentinel for the child entry cache representing a missing child list for * a given path to prevent looking for non-existing bundle entries multiple * times (value is an empty list). */ private static final List<String> NOT_FOUND_CHILDREN = Collections.<String> emptyList(); /** * Single entry cache. This is a synchronized map with a size limit. */ private final Map<String, URL> cache; /** * The child entry cache. This is a synchronized map with a size limit. */ private final Map<String, List<String>> listCache; /** * The Bundle providing the resource entries. */ private final Bundle bundle; // static initializer setting the NOT_FOUND_URL. Because the // constructor may throw an exception we use a static initializer // which fails the class initialization in the unlikely case // of the URL constructor failing. static { try { NOT_FOUND_URL = new URL("file:/not_found"); } catch (MalformedURLException mue) { throw new ExceptionInInitializerError(mue); } } /** * Creates a new instance of this class providing access to the entries in * the given <code>bundle</code>. * * @param bundle */ BundleResourceCache(Bundle bundle) { this.bundle = bundle; // create the limited maps wrapping in synchronized maps this.cache = Collections.synchronizedMap(new BundleResourceMap<String, URL>( CACHE_SIZE)); this.listCache = Collections.synchronizedMap(new BundleResourceMap<String, List<String>>( LIST_CACHE_SIZE)); } /** * Returns the <code>Bundle</code> to which this instance provides access. */ Bundle getBundle() { return bundle; } /** * Returns the entry in the underlying bundle at the given path. This path * is assumed to be an absolute path. If relative it is resolved relative to * the bundle root. * <p> * This method is backed by the <code>Bundle.getEntry(String)</code> * method. * * @param path The path to the bundle entry to return * @return The URL to access the bundle entry or <code>null</code> if the * bundle does not contain the request entry. */ URL getEntry(String path) { URL url = cache.get(path); if (url == null) { url = bundle.getEntry(path); if (url == null) { url = NOT_FOUND_URL; } cache.put(path, url); } return (url == NOT_FOUND_URL) ? null : url; } /** * Returns a list of bundle entry paths considered children of the given * <code>parentPath</code>. This parent path is assumed to be an absolute * path. If relative it is resolved relative to the bundle root. * <p> * This method is backed by the <code>Bundle.getEntryPaths(String)</code> * method but returns an <code>Iterator<String></code> instead of an * <code>Enumeration</code> of strings. * * @param parentPath The path to the parent entry whose child entries are to * be returned. * @return An <code>Iterator<String></code> providing the paths of * entries considered direct children of the <code>parentPath</code> * or <code>null</code> if the parent entry does not exist. */ Iterator<String> getEntryPaths(String path) { List<String> list = listCache.get(path); if (list == null) { @SuppressWarnings("unchecked") Enumeration<String> entries = bundle.getEntryPaths(path); if (entries != null && entries.hasMoreElements()) { list = new LinkedList<String>(); while (entries.hasMoreElements()) { list.add(entries.nextElement()); } } if (list == null) { list = NOT_FOUND_CHILDREN; } listCache.put(path, list); } return (list == NOT_FOUND_CHILDREN) ? null : list.iterator(); } // ---------- Management API /** * Returns the current number of entries stored in the entry cache. This * number includes "negative" entries, which are requested entries not found * in the bundle. */ int getEntryCacheSize() { return cache.size(); } /** * Returns the maximum number of entries to be stored in the cache. This * number is currently fixed at {@link #CACHE_SIZE} */ int getEntryCacheMaxSize() { return CACHE_SIZE; } /** * Returns the current number of list entries stored in the list cache. This * number includes "negative" list entries, which are requested list entries * not found in the bundle. */ int getListCacheSize() { return listCache.size(); } /** * Returns the maximum number of list entries to be stored in the cache. * This number is currently fixed at {@link #LIST_CACHE_SIZE} */ int getListCacheMaxSize() { return LIST_CACHE_SIZE; } // ---------- inner class /** * The <code>BundleResourceMap</code> class extends the * <code>LinkedHashMap</code> class overwriting the * {@link #removeEldestEntry(Entry)} method to implement the size limit, * which is set in the constructor. */ private static class BundleResourceMap<K, V> extends LinkedHashMap<String, V> { private static final long serialVersionUID = 7455098291380945276L; /** * The default size of a bundle resource cache (value is 20). */ private static final int DEFAULT_LIMIT = 20; /** * The limit configured for this map. */ private final int limit; /** * Creates a new instance of this size limited map. * * @param limit The maximum number of entries in this map. If this value * is less than or equal to zero, the default size of * {@link #DEFAULT_LIMIT} is used. */ BundleResourceMap(int limit) { // deliberately chosen initial size and load factor, but // we need the access-order to implement the LRU mechanism super(8, 0.75f, true); // normalize size to a possitive number if (limit <= 0) { limit = DEFAULT_LIMIT; } this.limit = limit; } /** * Returns <code>true</code> if the current number of elements in the * map exceeds the configured limit. */ @Override protected boolean removeEldestEntry(Entry<String, V> eldest) { return size() > limit; } } }