Author: fmeschbe
Date: Wed Sep 3 23:33:42 2008
New Revision: 691883
URL: http://svn.apache.org/viewvc?rev=691883&view=rev
Log:
SLING-641 Make the internal map of the cache be access-ordered (instead of
insert-ordered) and wrap the maps in synchronized maps to prevent
multi-threading issues plus JavaDoc and preparation for management support
Modified:
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java
Modified:
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java
URL:
http://svn.apache.org/viewvc/incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java?rev=691883&r1=691882&r2=691883&view=diff
==============================================================================
---
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java
(original)
+++
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java
Wed Sep 3 23:33:42 2008
@@ -26,59 +26,146 @@
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;
-public class BundleResourceCache {
-
+/**
+ * The <code>BundleResourceCache</code> implements a simple caching for
+ * resources provided from a bundle. Each [EMAIL PROTECTED]
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 [EMAIL PROTECTED] #CACHE_SIZE} for
the entries
+ * cache and at [EMAIL PROTECTED] #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();
- private final BundleResourceMap<String, URL> cache;
-
- private final BundleResourceMap<String, List<String>> listCache;
-
+ /**
+ * 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");
+ 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;
- this.cache = new BundleResourceMap<String, URL>(50);
- this.listCache = new BundleResourceMap<String, List<String>>(20);
+
+ // 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()) {
@@ -87,34 +174,101 @@
list.add(entries.nextElement());
}
}
-
+
if (list == null) {
list = NOT_FOUND_CHILDREN;
}
-
+
listCache.put(path, list);
}
-
+
return (list == NOT_FOUND_CHILDREN) ? null : list.iterator();
}
-
- private static class BundleResourceMap<K, V> extends LinkedHashMap<String,
V> {
+
+ // ---------- 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 [EMAIL PROTECTED] #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 [EMAIL PROTECTED] #LIST_CACHE_SIZE}
+ */
+ int getListCacheMaxSize() {
+ return LIST_CACHE_SIZE;
+ }
+
+ // ---------- inner class
+
+ /**
+ * The <code>BundleResourceMap</code> class extends the
+ * <code>LinkedHashMap</code> class overwriting the
+ * [EMAIL PROTECTED] #removeEldestEntry(Entry)} method to implement the
size limit,
+ * which is set in the constructor.
+ */
+ private static class BundleResourceMap<K, V> extends
+ LinkedHashMap<String, V> {
/**
- * The default size of a bundle resource cache.
+ * The default size of a bundle resource cache (value is 20).
+ */
+ private static final int DEFAULT_LIMIT = 20;
+
+ /**
+ * The limit configured for this map.
*/
- public static final int DEFAULT_LIMIT = 100;
-
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
+ * [EMAIL PROTECTED] #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 the maximum number of entries to be stored in this map.
+ */
+ int getLimit() {
+ return limit;
+ }
+
/**
* Returns <code>true</code> if the current number of elements in the
* map exceeds the configured limit.
Modified:
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java
URL:
http://svn.apache.org/viewvc/incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java?rev=691883&r1=691882&r2=691883&view=diff
==============================================================================
---
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java
(original)
+++
incubator/sling/trunk/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java
Wed Sep 3 23:33:42 2008
@@ -48,7 +48,7 @@
private final MappedPath[] roots;
private ServiceRegistration serviceRegistration;
-
+
/**
* Creates Bundle resource provider accessing entries in the given Bundle
an
* supporting resources below root paths given by the rootList which is a
@@ -70,38 +70,51 @@
void registerService(BundleContext context) {
Dictionary<String, Object> props = new Hashtable<String, Object>();
- props.put(Constants.SERVICE_DESCRIPTION, "Provider of Bundle based
Resources");
+ props.put(Constants.SERVICE_DESCRIPTION,
+ "Provider of Bundle based Resources");
props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
props.put(ROOTS, getRoots());
-
+
serviceRegistration = context.registerService(SERVICE_NAME, this,
props);
}
-
+
void unregisterService() {
if (serviceRegistration != null) {
serviceRegistration.unregister();
}
}
+
+ //---------- Web Console plugin support
+
+ BundleResourceCache getBundleResourceCache() {
+ return bundle;
+ }
+
+ MappedPath[] getMappedPaths() {
+ return roots;
+ }
+
+ //---------- internal
/** Returns the root paths */
private String[] getRoots() {
String[] rootPaths = new String[roots.length];
- for (int i=0; i < roots.length; i++) {
+ for (int i = 0; i < roots.length; i++) {
rootPaths[i] = roots[i].getResourceRoot();
}
return rootPaths;
}
-
+
private MappedPath getMappedPath(String resourcePath) {
- for (MappedPath mappedPath: roots) {
+ for (MappedPath mappedPath : roots) {
if (mappedPath.isChild(resourcePath)) {
return mappedPath;
}
}
-
+
return null;
}
-
+
public Resource getResource(ResourceResolver resourceResolver,
HttpServletRequest request, String path) {
return getResource(resourceResolver, path);
@@ -115,9 +128,10 @@
public Resource getResource(ResourceResolver resourceResolver, String
path) {
MappedPath mappedPath = getMappedPath(path);
if (mappedPath != null) {
- return BundleResource.getResource(resourceResolver, bundle,
mappedPath, path);
+ return BundleResource.getResource(resourceResolver, bundle,
+ mappedPath, path);
}
-
+
return null;
}
@@ -140,6 +154,6 @@
// the parent resource cannot have children in this provider,
// though this is basically not expected, we still have to
// be prepared for such a situation
- return Collections.<Resource>emptyList().iterator();
+ return Collections.<Resource> emptyList().iterator();
}
}