blob: ba9d1f5deda3f5ab05276caa26a74821058a250c [file] [log] [blame]
/*
* 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;
}
}
}