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 {

Reply via email to