Author: ivol37 at gmail.com
Date: Mon Nov 15 12:16:23 2010
New Revision: 425
Log:
[AMDATU-82] Implemented a simple in-memory cache of gadgetspecs with a default
timeout of 15 minutes. Cache is cleared when the service is restarted.
Modified:
trunk/amdatu-opensocial/gadgetmanagement/src/main/java/org/amdatu/opensocial/gadgetmanagement/service/GadgetManagementServiceImpl.java
Modified:
trunk/amdatu-opensocial/gadgetmanagement/src/main/java/org/amdatu/opensocial/gadgetmanagement/service/GadgetManagementServiceImpl.java
==============================================================================
---
trunk/amdatu-opensocial/gadgetmanagement/src/main/java/org/amdatu/opensocial/gadgetmanagement/service/GadgetManagementServiceImpl.java
(original)
+++
trunk/amdatu-opensocial/gadgetmanagement/src/main/java/org/amdatu/opensocial/gadgetmanagement/service/GadgetManagementServiceImpl.java
Mon Nov 15 12:16:23 2010
@@ -29,6 +29,12 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DefaultValue;
@@ -41,19 +47,19 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import org.amdatu.authorization.login.service.LoginService;
+import org.amdatu.core.cassandra.persistencemanager.CassandraException;
+import org.amdatu.libraries.utilities.ConversionUtil;
import org.amdatu.opensocial.gadgetmanagement.DefaultGadgetRepository;
import org.amdatu.opensocial.gadgetmanagement.GadgetManagement;
import org.amdatu.opensocial.gadgetmanagement.osgi.Activator;
-import org.amdatu.libraries.utilities.ConversionUtil;
-import org.amdatu.authorization.login.service.LoginService;
-import org.amdatu.core.cassandra.persistencemanager.CassandraException;
-import org.amdatu.web.httpcontext.BasicHttpSession;
-import org.amdatu.web.httpcontext.HttpContextServiceFactory;
-import org.amdatu.web.httpcontext.ResourceProvider;
import org.amdatu.opensocial.shindig.GadgetCategory;
import org.amdatu.opensocial.shindig.GadgetDefinition;
import org.amdatu.opensocial.shindig.GadgetStore;
import org.amdatu.opensocial.shindig.OpenSocialConstants;
+import org.amdatu.web.httpcontext.BasicHttpSession;
+import org.amdatu.web.httpcontext.HttpContextServiceFactory;
+import org.amdatu.web.httpcontext.ResourceProvider;
import org.apache.felix.dm.Component;
import org.apache.shindig.auth.BlobCrypterSecurityToken;
import org.apache.shindig.common.crypto.BasicBlobCrypter;
@@ -78,8 +84,12 @@
@Path("gadgetstore")
public class GadgetManagementServiceImpl implements GadgetManagement,
ResourceProvider {
// Timeout for retrieving gadget specs
- private final static int TIMEOUT = 5000;
-
+ private final static int GADGETSPEC_READ_TIMEOUT = 5000;
+
+ // Timeout of the gadgetspec cache in milleseconds; when the gadgetspec in
the cache is older then this it will
+ // be removed from the cache. Default is 15 minutes.
+ private final static int GADGETSPEC_CACHE_TIMEOUT = 15 * 60 * 1000;
+
private static final String[] GADGET_MODULEPREFS =
new String[] { "title", "description", "author", "author_email",
"author_affiliation", "screenshot",
"title_url" };
@@ -92,10 +102,14 @@
private volatile GadgetStore m_gadgetStore;
private Component m_httpContextComponent;
-
+
// Use the startId as prefix for the gadget id to ensure each gadget has a
unique id
private static int m_startId = 1;
+ // Thread safe implementation of gadget spec cache
+ private ConcurrentMap<URL, FutureTask<Object[]>> m_gadgetSpecCache =
+ new ConcurrentHashMap<URL, FutureTask<Object[]>>();
+
/**
* The init() method is invoked by the Felix dependency manager.
*/
@@ -301,10 +315,11 @@
// For now just return all gadgets, but with additional security
token
int column = 1;
for (String gadgetId : gadgetIds) {
- JSONObject gadget;
+ JSONObject gadget;
if (generateStartId) {
gadget = retrieveGadget(gadgetId);
- } else {
+ }
+ else {
gadget =
retrieveGadget(gadgetId.substring(gadgetId.indexOf("-") + 1));
}
if (gadget != null) {
@@ -325,7 +340,7 @@
// Assign id of the gadget, which is startId - [gadget url]
setGadgetId(gadget, gadgetId, generateStartId);
-
+
gadgets.add(gadget);
}
}
@@ -354,7 +369,7 @@
return Response.ok(jsonObject.toString(),
MediaType.APPLICATION_JSON_TYPE).build();
}
-
+
private void setGadgetId(JSONObject gadget, String gadgetId, boolean
generateStartId) throws JSONException {
String id, url;
if (generateStartId) {
@@ -362,7 +377,8 @@
id = new Integer(m_startId++).toString();
url = gadgetId;
gadget.put("id", id + "-" + url);
- } else {
+ }
+ else {
// For persistent gadgets, use persisted startId
gadget.put("id", gadgetId);
}
@@ -373,7 +389,7 @@
try {
m_logService.log(LogService.LOG_DEBUG, "Retrieving gadgetspec for
'" + gadgetUrl + "'");
- String xml = loadXML(new URL(gadgetUrl));
+ String xml = loadXMLFromCache(new URL(gadgetUrl));
GadgetSpec spec = new GadgetSpec(Uri.parse(gadgetUrl), xml);
// Perform message substitution if locales are available
@@ -384,7 +400,7 @@
Locale locale = getPreferredLocale(defaultLocale, locales);
// First replace messages with the preferred language labels
- String messageXML = loadXML(new
URL(locales.get(locale).getMessages().toString()));
+ String messageXML = internalLoadXML(new
URL(locales.get(locale).getMessages().toString()));
MessageBundle bundle = new MessageBundle(locales.get(locale),
messageXML);
Substitutions substituter = new Substitutions();
substituter.addSubstitutions(Substitutions.Type.MESSAGE,
bundle.getMessages());
@@ -392,7 +408,7 @@
// Replace the remaining messages with 'all' messages
locale = getPreferredLocale(new Locale("all", "all"), locales);
- messageXML = loadXML(new
URL(locales.get(locale).getMessages().toString()));
+ messageXML = internalLoadXML(new
URL(locales.get(locale).getMessages().toString()));
bundle = new MessageBundle(locales.get(locale), messageXML);
substituter = new Substitutions();
substituter.addSubstitutions(Substitutions.Type.MESSAGE,
bundle.getMessages());
@@ -429,23 +445,68 @@
return Activator.RESOURCE_ID;
}
- private String loadXML(URL url) {
+ // Return XML from cache or retrieve it
+ private String loadXMLFromCache(final URL url) {
+ FutureTask<Object[]> futureTask = m_gadgetSpecCache.get(url);
+ try {
+ if (futureTask != null) {
+ // Check if the timestamp is out-of-date
+ long timestamp = (Long) futureTask.get()[0];
+ if (System.currentTimeMillis() - timestamp >
GADGETSPEC_CACHE_TIMEOUT) {
+ m_gadgetSpecCache.remove(url);
+ futureTask = null;
+ m_logService.log(LogService.LOG_DEBUG, "Removed gadgetspec
'" + url
+ + "' from cache, it is out of date. Gadgetspec will be
re-retrieved.");
+ }
+ }
+ if (futureTask == null) {
+ Callable<Object[]> callable = new Callable<Object[]>() {
+ public Object[] call() throws Exception {
+ return new Object[] { System.currentTimeMillis(),
internalLoadXML(url) };
+ }
+ };
+ FutureTask<Object[]> ft = new FutureTask<Object[]>(callable);
+ futureTask = m_gadgetSpecCache.putIfAbsent(url, ft);
+ if (futureTask == null) {
+ futureTask = ft;
+ ft.run();
+ }
+ }
+ return (String) futureTask.get()[1];
+ }
+ catch (CancellationException e) {
+ if (futureTask != null) {
+ m_gadgetSpecCache.remove(url, futureTask);
+ }
+ }
+ catch (InterruptedException e) {
+ m_logService.log(LogService.LOG_ERROR, "Retrieving gadgetspec for
'" + url + "' interrupted", e);
+ }
+ catch (ExecutionException e) {
+ m_logService.log(LogService.LOG_ERROR, "Execution failed
retrieving gadgetspec for '" + url + "'", e);
+ }
+ return null;
+ }
+
+ // Loads the XML without cache
+ private String internalLoadXML(URL url) {
long time = System.currentTimeMillis();
BufferedReader reader = null;
try {
try {
String xml = "";
URLConnection inputConnection = url.openConnection();
- inputConnection.setReadTimeout(TIMEOUT);
+ inputConnection.setReadTimeout(GADGETSPEC_READ_TIMEOUT);
// TODO: assuming here it is UTF-8
reader =
new BufferedReader(
- new
InputStreamReader(inputConnection.getInputStream(), Charset.forName("UTF-8")));
+ new InputStreamReader(inputConnection.getInputStream(),
Charset.forName("UTF-8")));
String inputLine;
while ((inputLine = reader.readLine()) != null) {
xml += inputLine;
}
- m_logService.log(LogService.LOG_DEBUG, "Retrieving gadgetspec
'" + url + "' took " + (System.currentTimeMillis()-time) + " ms");
+ m_logService.log(LogService.LOG_DEBUG, "Retrieving gadgetspec
'" + url + "' took "
+ + (System.currentTimeMillis() - time) + " ms");
return xml;
}
finally {