Author: nbelaevski
Date: 2008-11-28 15:18:11 -0500 (Fri, 28 Nov 2008)
New Revision: 11449
Modified:
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/CachedResourceBuilder.java
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/DualLRUMap.java
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBean.java
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBytesDataBean.java
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceDataBean.java
Log:
RF-3586
Modified:
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/CachedResourceBuilder.java
===================================================================
---
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/CachedResourceBuilder.java 2008-11-28
19:06:24 UTC (rev 11448)
+++
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/CachedResourceBuilder.java 2008-11-28
20:18:11 UTC (rev 11449)
@@ -24,6 +24,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
@@ -37,165 +39,241 @@
import org.apache.commons.logging.LogFactory;
/**
- * @author shura
+ * This class is intended to generate predictable URIs for all resources handled by
RichFaces.
+ * It creates mapping between resource key/data value and generated random string of
known format
+ * for all resource requests. By default {@link UUID#toString()} is used. Mapping is
maintained by LRU map
+ * having default capacity of {@value #DEFAULT_CAPACITY} so be aware that stale entries
can be removed and
+ * application users will get errors then.
*
+ * How to use: add to application classpath
META-INF/services/org.ajax4jsf.resource.InternetResourceBuilder
+ * file with the following content
<code>org.ajax4jsf.resource.cached.CachedResourceBuilder</code>
+ *
+ * Limitations:
+ *
+ * <ol>
+ * <li>Doesn't work in clustered environments</li>
+ * <li>All resource URIs become invalid after server restart that can cause cache
issues</li>
+ * <li>
+ * Diagnostic of resource loading errors becomes somewhat harder. Variant of code where
random key
+ * is appended to resource name doesn't satisfy the requirement of no path depth
> 8 as requested
+ * by users (see <a
href="https://jira.jboss.org/jira/browse/RF-3586">RF-3586<... for more
info)
+ * </li>
+ * </ol>
+ *
+ * @author Alexander Smirnov
+ * @author Nick Belaevski
*/
public class CachedResourceBuilder extends ResourceBuilderImpl {
- private static final Log log = LogFactory
- .getLog(CachedResourceBuilder.class);
+ private static final Log log = LogFactory.getLog(CachedResourceBuilder.class);
- private static final int DEFAULT_CAPACITY = 10000;
+ protected static final int DEFAULT_CAPACITY = 10000;
- private long counter = 0;
+ private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+
+ private DualLRUMap cache;
- private DualLRUMap cache;
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.ajax4jsf.resource.ResourceBuilderImpl#decrypt(byte[])
+ */
+ protected byte[] decrypt(byte[] data) {
+ // dummy - data not send via internet.
+ return data;
+ }
- /*
- * (non-Javadoc)
- *
- * @see org.ajax4jsf.resource.ResourceBuilderImpl#decrypt(byte[])
- */
- protected byte[] decrypt(byte[] data) {
- // dummy - data not send via internet.
- return data;
- }
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.ajax4jsf.resource.ResourceBuilderImpl#encrypt(byte[])
+ */
+ protected byte[] encrypt(byte[] data) {
+ // dummy - data not send via internet.
+ return data;
+ }
- /*
- * (non-Javadoc)
- *
- * @see org.ajax4jsf.resource.ResourceBuilderImpl#encrypt(byte[])
- */
- protected byte[] encrypt(byte[] data) {
- // dummy - data not send via internet.
- return data;
- }
+ /*
+ * (non-Javadoc)
+ *
+ * @see
org.ajax4jsf.resource.ResourceBuilderImpl#getResourceDataForKey(java.lang.String)
+ */
+ public Object getResourceDataForKey(String key) {
+ ResourceBean bean = null;
+ try {
+ readWriteLock.readLock().lock();
+ bean = (ResourceBean) cache.get(key);
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
- /*
- * (non-Javadoc)
- *
- * @see
org.ajax4jsf.resource.ResourceBuilderImpl#getResourceDataForKey(java.lang.String)
- */
- public Object getResourceDataForKey(String key) {
- ResourceBean bean = (ResourceBean) cache.get(key);
- if (null == bean) {
- throw new ResourceNotFoundException("Resource for key " + key
- + "not present in cache");
+ if (null == bean) {
+ throw new ResourceNotFoundException("Resource for key " + key
+ + "not present in cache");
+ }
+
+ return bean.getData();
}
- return bean.getData();
- }
- /*
- * (non-Javadoc)
- *
- * @see
org.ajax4jsf.resource.ResourceBuilderImpl#getResourceForKey(java.lang.String)
- */
- public InternetResource getResourceForKey(String key)
- throws ResourceNotFoundException {
- ResourceBean bean = (ResourceBean) cache.get(key);
- if (null == bean) {
- throw new ResourceNotFoundException("Resource for key " + key
- + "not present in cache");
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.ajax4jsf.resource.ResourceBuilderImpl#getResourceForKey(java.lang.String)
+ */
+ public InternetResource getResourceForKey(String key)
+ throws ResourceNotFoundException {
+ ResourceBean bean = null;
+ try {
+ readWriteLock.readLock().lock();
+ bean = (ResourceBean) cache.get(key);
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+
+ if (null == bean) {
+ throw new ResourceNotFoundException("Resource for key " + key
+ + "not present in cache");
+ }
+
+ return super.getResourceForKey(bean.getKey());
}
- return super.getResourceForKey(bean.getKey());
- }
- /*
- * (non-Javadoc)
- *
- * @see
org.ajax4jsf.resource.ResourceBuilderImpl#getUri(org.ajax4jsf.resource.InternetResource,
- * javax.faces.context.FacesContext, java.lang.Object)
- */
- public String getUri(InternetResource resource, FacesContext facesContext,
- Object data) {
- ResourceBean bean;
- if (null == data) {
- bean = new ResourceBean(resource.getKey());
- } else {
- if (data instanceof byte[]) {
- // Special case for simple bytes array data.
- bean = new ResourceBytesDataBean(resource.getKey(),
- (byte[]) data);
- } else {
- bean = new ResourceDataBean(resource.getKey(), data);
- }
+ /*
+ * (non-Javadoc)
+ *
+ * @see
org.ajax4jsf.resource.ResourceBuilderImpl#getUri(org.ajax4jsf.resource.InternetResource,
+ * javax.faces.context.FacesContext, java.lang.Object)
+ */
+ public String getUri(InternetResource resource, FacesContext facesContext,
+ Object data) {
+ ResourceBean bean;
+ if (null == data) {
+ bean = new ResourceBean(resource.getKey());
+ } else {
+ if (data instanceof byte[]) {
+ // Special case for simple bytes array data.
+ bean = new ResourceBytesDataBean(resource.getKey(),
+ (byte[]) data);
+ } else {
+ bean = new ResourceDataBean(resource.getKey(), data);
+ }
+ }
+
+ String key = null;
+
+ try {
+ readWriteLock.readLock().lock();
+
+ key = (String) cache.getKey(bean);
+
+ if (key != null) {
+ // Refresh LRU
+ cache.get(key);
+ }
+ } finally {
+ readWriteLock.readLock().unlock();
+ }
+
+ if (key == null) {
+ try {
+ readWriteLock.writeLock().lock();
+
+ key = (String) cache.getKey(bean);
+ if (null == key) {
+ key = createNextKey();
+ while (cache.containsKey(key)) {
+ key = createNextKey();
+ }
+
+ cache.put(key, bean);
+ } else {
+ // Refresh LRU
+ cache.get(key);
+ }
+
+ } finally {
+ readWriteLock.writeLock().unlock();
+ }
+ }
+
+ boolean isGlobal = !resource.isSessionAware();
+
+ String resourceURL = getFacesResourceURL(facesContext, key, isGlobal);
+ if (!isGlobal) {
+ resourceURL = facesContext.getExternalContext().encodeResourceURL(
+ resourceURL);
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(Messages.getMessage(Messages.BUILD_RESOURCE_URI_INFO,
+ resource.getKey(), resourceURL));
+ }
+ return resourceURL;// context.getExternalContext().encodeResourceURL(resourceURL);
}
- String key = (String) cache.getKey(bean);
- if (null == key) {
- synchronized (this) {
- counter++;
- key = bean.hashCode() + "c" + counter;
- }
- cache.put(key, bean);
- } else {
- // Refresh LRU
- cache.get(key);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.ajax4jsf.resource.ResourceBuilderImpl#init(javax.servlet.ServletContext,
+ * java.lang.String)
+ */
+ public void init() throws FacesException {
+ super.init();
+
+ Properties properties = getProperties("cache.properties");
+ int capacity = getCapacity(properties);
+ if (capacity <= 0) {
+ capacity = DEFAULT_CAPACITY;
+ log.info("Using default capacity: " + DEFAULT_CAPACITY);
+ }
+
+ cache = new DualLRUMap(capacity);
}
+
+ /**
+ * Get properties file from classpath
+ *
+ * @param name
+ * @return
+ */
+ protected Properties getProperties(String name) {
+ Properties properties = new Properties();
+ InputStream props = URLToStreamHelper.urlToStreamSafe(CachedResourceBuilder.class
+ .getResource(name));
+ if (null != props) {
+ try {
+ properties.load(props);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ log.warn(Messages.getMessage(Messages.READING_PROPERTIES_ERROR,
+ name), e);
+ } finally {
+ try {
+ props.close();
+ } catch (IOException e) {
+ // Can be ignored
+ }
+ }
+ }
+ return properties;
+
+ }
- boolean isGlobal = !resource.isSessionAware();
-
- String resourceURL = getFacesResourceURL(facesContext, key, isGlobal);
- if (!isGlobal) {
- resourceURL = facesContext.getExternalContext().encodeResourceURL(
- resourceURL);
+ protected String createNextKey() {
+ return UUID.randomUUID().toString();
}
- if (log.isDebugEnabled()) {
- log.debug(Messages.getMessage(Messages.BUILD_RESOURCE_URI_INFO,
- resource.getKey(), resourceURL));
- }
- return resourceURL;// context.getExternalContext().encodeResourceURL(resourceURL);
- }
- /*
- * (non-Javadoc)
- *
- * @see
org.ajax4jsf.resource.ResourceBuilderImpl#init(javax.servlet.ServletContext,
- * java.lang.String)
- */
- public void init()
- throws FacesException {
- super.init();
- // Create cache manager.
- Properties properties = getProperties("cache.properties");
- int capacity = DEFAULT_CAPACITY;
- String capacityString = properties.getProperty("cache.capacity");
- if (null != capacityString) {
- try {
- capacity = Integer.parseInt(capacityString);
- } catch (NumberFormatException e) {
- log.warn("Error parsing value of parameters cache capacity, use default value
"+DEFAULT_CAPACITY, e);
- }
- }
- cache = new DualLRUMap(capacity);
- counter = getStartTime() - 1158760000000L;
- }
-
- /**
- * Get properties file from classpath
- *
- * @param name
- * @return
- */
- protected Properties getProperties(String name) {
- Properties properties = new Properties();
- InputStream props = URLToStreamHelper.urlToStreamSafe(CachedResourceBuilder.class
- .getResource(name));
- if (null != props) {
- try {
- properties.load(props);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- log.warn(Messages.getMessage(Messages.READING_PROPERTIES_ERROR,
- name), e);
- } finally {
- try {
- props.close();
- } catch (IOException e) {
- // Can be ignored
+ protected int getCapacity(Properties properties) {
+ // Create cache manager.
+ int capacity = 0;
+ String capacityString = properties.getProperty("cache.capacity");
+ if (null != capacityString) {
+ try {
+ capacity = Integer.parseInt(capacityString);
+ } catch (NumberFormatException e) {
+ log.warn("Error parsing value of parameters cache capacity", e);
+ }
}
- }
+
+ return capacity;
}
- return properties;
-
- }
}
Modified: trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/DualLRUMap.java
===================================================================
---
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/DualLRUMap.java 2008-11-28
19:06:24 UTC (rev 11448)
+++
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/DualLRUMap.java 2008-11-28
20:18:11 UTC (rev 11449)
@@ -22,57 +22,48 @@
package org.ajax4jsf.resource.cached;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
-import org.apache.commons.collections.LRUMap;
+class DualLRUMap<K, V> extends LinkedHashMap<K, V> {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -313747679711995782L;
-class DualLRUMap extends LRUMap {
+ private Map<V, K> reverseMap;
- private Map reverseMap ;
+ private int capacity;
+
+ public DualLRUMap(int capacity) {
+ super(capacity, 0.75f, true);
- public DualLRUMap(int size) {
- super(size);
- reverseMap = new HashMap(size);
+ this.capacity = capacity;
+
+ this.reverseMap = new HashMap<V, K>(capacity, 0.75f);
}
-
- /* (non-Javadoc)
- * @see org.apache.commons.collections.LRUMap#processRemovedLRU(java.lang.Object,
java.lang.Object)
- */
- protected void processRemovedLRU(Object key, Object value) {
- synchronized (this) {
- super.processRemovedLRU(key, value);
- reverseMap.remove(value);
- }
- }
-
- /* (non-Javadoc)
- * @see org.apache.commons.collections.LRUMap#put(java.lang.Object, java.lang.Object)
- */
- public Object put(Object key, Object value) {
- synchronized (this) {
- reverseMap.put(value, key);
- return super.put(key, value);
- }
- }
- public Object getKey(Object value){
- synchronized (this) {
- Object key = reverseMap.get(value);
- if(!containsKey(key)){
- reverseMap.remove(value);
- key = null;
- }
- return key;
- }
+ public V put(K key, V value) {
+ V v = super.put(key, value);
+
+ reverseMap.put(value, key);
+
+ return v;
+ };
+
+ public K getKey(Object key) {
+ return reverseMap.get(key);
}
+
+ @Override
+ protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
+ boolean remove = (size() > capacity);
- /* (non-Javadoc)
- * @see org.apache.commons.collections.LRUMap#get(java.lang.Object)
- */
- public Object get(Object key) {
- synchronized (this) {
- return super.get(key);
+ if (remove) {
+ reverseMap.remove(eldest.getValue());
}
+
+ return remove;
}
-
}
\ No newline at end of file
Modified:
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBean.java
===================================================================
---
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBean.java 2008-11-28
19:06:24 UTC (rev 11448)
+++
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBean.java 2008-11-28
20:18:11 UTC (rev 11449)
@@ -27,8 +27,12 @@
* @author shura
*
*/
-public class ResourceBean implements Serializable {
+public class ResourceBean implements Serializable {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 2830008963777271324L;
private String key;
/**
@@ -51,10 +55,15 @@
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
if (null != obj && obj instanceof ResourceBean) {
ResourceBean bean = (ResourceBean) obj;
return key.equals(bean.getKey()) && bean.getData() == null;
- };
+ }
+
return false;
}
Modified:
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBytesDataBean.java
===================================================================
---
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBytesDataBean.java 2008-11-28
19:06:24 UTC (rev 11448)
+++
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceBytesDataBean.java 2008-11-28
20:18:11 UTC (rev 11449)
@@ -29,6 +29,10 @@
*/
public class ResourceBytesDataBean extends ResourceBean {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -3012554202964229624L;
private byte[] data;
ResourceBytesDataBean(String key, byte[] data){
@@ -43,11 +47,16 @@
* @see com.exadel.vcp.resource.ResourceBean#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
if (null != obj && obj instanceof ResourceBytesDataBean) {
ResourceBytesDataBean bean = (ResourceBytesDataBean) obj;
byte[] beanData = (byte[]) bean.getData();
return getKey().equals(bean.getKey()) && Arrays.equals(data, beanData);
- };
+ }
+
return false;
}
@@ -55,11 +64,10 @@
* @see com.exadel.vcp.resource.ResourceBean#hashCode()
*/
public int hashCode() {
- int hash = super.hashCode();
- for (int i = 0; i < data.length; i++) {
- hash = hash*32+data[i];
- }
- return hash;
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(data);
+ return result;
}
}
Modified:
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceDataBean.java
===================================================================
---
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceDataBean.java 2008-11-28
19:06:24 UTC (rev 11448)
+++
trunk/framework/impl/src/main/java/org/ajax4jsf/resource/cached/ResourceDataBean.java 2008-11-28
20:18:11 UTC (rev 11449)
@@ -26,8 +26,13 @@
* @author shura
*
*/
-public class ResourceDataBean extends ResourceBean{
+public class ResourceDataBean extends ResourceBean {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -6486715556040103424L;
+
private Object data;
public ResourceDataBean(String key, Object data){
@@ -39,10 +44,15 @@
* @see com.exadel.vcp.resource.ResourceBean#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
if (null != obj && obj instanceof ResourceBean) {
ResourceBean bean = (ResourceBean) obj;
return getKey().equals(bean.getKey()) && data.equals(bean.getData());
- };
+ }
+
return false;
}
@@ -57,8 +67,10 @@
* @see com.exadel.vcp.resource.ResourceBean#hashCode()
*/
public int hashCode() {
- // TODO Auto-generated method stub
- return super.hashCode()*32+data.hashCode();
+ final int prime = 31;
+ int result = super.hashCode();
+ result = result * prime + data.hashCode();
+ return result;
}
}