Author: johnh
Date: Fri Feb 25 20:35:12 2011
New Revision: 1074690

URL: http://svn.apache.org/viewvc?rev=1074690&view=rev
Log:
Replace document cache with a per-module cache in CajaContentRewriter.

This uses the existing object cache factory to create a cache from
module keys (implemented as MD5 hashes over parse trees) to module
content.

This requires an outstanding minor API change to PluginCompiler and
cannot be deployed until we push a new version of caja to maven.

Patch provided by Mike Samuel.


Added:
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCache.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKey.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKeys.java
    
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ModuleCacheTest.java
Modified:
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
    shindig/trunk/pom.xml

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java?rev=1074690&r1=1074689&r2=1074690&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
 Fri Feb 25 20:35:12 2011
@@ -35,6 +35,7 @@ import com.google.caja.parser.html.DomPa
 import com.google.caja.parser.html.Namespaces;
 import com.google.caja.parser.js.CajoledModule;
 import com.google.caja.parser.js.Parser;
+import com.google.caja.plugin.Job;
 import com.google.caja.plugin.PipelineMaker;
 import com.google.caja.plugin.PluginCompiler;
 import com.google.caja.plugin.PluginMeta;
@@ -97,29 +98,29 @@ import java.util.logging.Logger;
  * A GadgetRewriter based on caja technology
  */
 public class CajaContentRewriter implements GadgetRewriter {
-  public static final String CAJOLED_DOCUMENTS = "cajoledDocuments";
+  public static final String CAJOLED_MODULES = "cajoledModules";
 
   //class name for logging purpose
-  private static final String classname = CajaContentRewriter.class.getName();
-  private static final Logger LOG = 
Logger.getLogger(classname,MessageKeys.MESSAGES);
+  private static final String CLASS_NAME = CajaContentRewriter.class.getName();
+  private static final Logger LOG = Logger.getLogger(CLASS_NAME, 
MessageKeys.MESSAGES);
 
 
-  private final Cache<String, Element> cajoledCache;
+  private final Cache<ModuleCacheKey, ImmutableList<Job>> moduleCache;
   private final RequestPipeline requestPipeline;
   private final HtmlSerializer htmlSerializer;
   private final ProxyUriManager proxyUriManager;
 
   @Inject
   public CajaContentRewriter(CacheProvider cacheProvider, RequestPipeline 
requestPipeline,
-      HtmlSerializer htmlSerializer, ProxyUriManager proxyUriManager) {
+                             HtmlSerializer htmlSerializer, ProxyUriManager 
proxyUriManager) {
     if (null == cacheProvider) {
-      this.cajoledCache = null;
+      this.moduleCache = null;
     } else {
-      this.cajoledCache = cacheProvider.createCache(CAJOLED_DOCUMENTS);
+      this.moduleCache = cacheProvider.createCache(CAJOLED_MODULES);
     }
     if (LOG.isLoggable(Level.INFO)) {
-      LOG.logp(Level.INFO, classname, "CajaContentRewriter", 
MessageKeys.CAJOLED_CACHE_CREATED,
-          new Object[] {cajoledCache});
+      LOG.logp(Level.INFO, CLASS_NAME, "CajaContentRewriter", 
MessageKeys.CAJOLED_CACHE_CREATED,
+               new Object[] {moduleCache});
     }
     this.requestPipeline = requestPipeline;
     this.htmlSerializer = htmlSerializer;
@@ -144,7 +145,7 @@ public class CajaContentRewriter impleme
   }
 
   @VisibleForTesting
-  ParseTreeNode parse(InputSource is, CharProducer cp, String mime, 
MessageQueue mq)
+  static ParseTreeNode parse(InputSource is, CharProducer cp, String mime, 
MessageQueue mq)
       throws ParseException {
     ParseTreeNode ptn;
     if (mime.contains("javascript")) {
@@ -171,11 +172,11 @@ public class CajaContentRewriter impleme
       UriFetcher fetcher = makeFetcher(uri, container);
       ExternalReference extRef = new ExternalReference(javaUri,
           FilePosition.instance(is, /*lineNo*/ 1, /*charInFile*/ 1, 
/*charInLine*/ 1));
-        // If the fetch fails, a UriFetchException is thrown and serialized as 
part of the
-        // message queue.
-        CharProducer cp = fetcher.fetch(extRef, mime).getTextualContent();
-        ParseTreeNode ptn = parse(is, cp, mime, mq);
-        return rewrite(uri, container, ptn, es53, debug);
+      // If the fetch fails, a UriFetchException is thrown and serialized as 
part of the
+      // message queue.
+      CharProducer cp = fetcher.fetch(extRef, mime).getTextualContent();
+      ParseTreeNode ptn = parse(is, cp, mime, mq);
+      return rewrite(uri, container, ptn, es53, debug);
     } catch (UnsupportedEncodingException e) {
       LOG.severe("Unexpected inability to recognize mime type: " + mime);
       mq.addMessage(ServiceMessageType.UNEXPECTED_INPUT_MIME_TYPE,
@@ -198,6 +199,9 @@ public class CajaContentRewriter impleme
     PluginMeta meta = new PluginMeta(fetcher, policy);
     PluginCompiler compiler = makePluginCompiler(meta, mq);
     compiler.setMessageContext(context);
+    if (moduleCache != null) {
+      compiler.setJobCache(new ModuleCache(moduleCache));
+    }
 
     if (debug) {
       compiler.setGoals(compiler.getGoals()
@@ -227,19 +231,10 @@ public class CajaContentRewriter impleme
 
     // Serialize outside of MutableContent, to prevent a re-parse.
     String docContent = HtmlSerialization.serialize(doc);
-    String cacheKey = HashUtil.checksum(docContent.getBytes());
     Node root = doc.createDocumentFragment();
     root.appendChild(doc.getDocumentElement());
 
     Node cajoledData = null;
-    if (cajoledCache != null && !debug) {
-      Element cajoledOutput = cajoledCache.getElement(cacheKey);
-      if (cajoledOutput != null) {
-        cajoledData = doc.adoptNode(cajoledOutput);
-        createContainerFor(doc, cajoledData);
-        mc.documentChanged();
-      }
-    }
 
     if (cajoledData == null) {
       if (debug) {
@@ -286,9 +281,6 @@ public class CajaContentRewriter impleme
         Element messagesNode = formatErrors(doc, is, docContent, messages,
             /* invisible */ false);
         cajoledOutput.appendChild(messagesNode);
-        if (cajoledCache != null && !debug) {
-          cajoledCache.addElement(cacheKey, cajoledOutput);
-        }
 
         cajoledData = cajoledOutput;
         createContainerFor(doc, cajoledData);
@@ -316,7 +308,7 @@ public class CajaContentRewriter impleme
       public FetchedData fetch(ExternalReference ref, String mimeType)
           throws UriFetchException {
         if (LOG.isLoggable(Level.INFO)) {
-          LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.RETRIEVE_REFERENCE,
+          LOG.logp(Level.INFO, CLASS_NAME, "makeFetcher", 
MessageKeys.RETRIEVE_REFERENCE,
               new Object[] {ref.toString()});
         }
         Uri resourceUri = gadgetUri.resolve(Uri.fromJavaUri(ref.getUri()));
@@ -329,13 +321,13 @@ public class CajaContentRewriter impleme
               new InputSource(ref.getUri()));
         } catch (GadgetException e) {
           if (LOG.isLoggable(Level.INFO)) {
-            LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.FAILED_TO_RETRIEVE,
+            LOG.logp(Level.INFO, CLASS_NAME, "makeFetcher", 
MessageKeys.FAILED_TO_RETRIEVE,
                 new Object[] {ref.toString()});
           }
           throw new UriFetchException(ref, mimeType, e);
         } catch (IOException e) {
           if (LOG.isLoggable(Level.INFO)) {
-            LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.FAILED_TO_READ,
+            LOG.logp(Level.INFO, CLASS_NAME, "makeFetcher", 
MessageKeys.FAILED_TO_READ,
                 new Object[] {ref.toString()});
           }
           throw new UriFetchException(ref, mimeType, e);
@@ -428,7 +420,7 @@ public class CajaContentRewriter impleme
       errbuilder.append(m.format(mc)).append('\n');
     }
     if (LOG.isLoggable(Level.INFO)) {
-      LOG.logp(Level.INFO, classname, methodname, MessageKeys.UNABLE_TO_CAJOLE,
+      LOG.logp(Level.INFO, CLASS_NAME, methodname, 
MessageKeys.UNABLE_TO_CAJOLE,
           new Object[] {errbuilder});
     }
   }

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java?rev=1074690&r1=1074689&r2=1074690&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java
 Fri Feb 25 20:35:12 2011
@@ -111,7 +111,7 @@ public class HttpRequestHandler {
       Provider<FeedProcessor> feedProcessorProvider) {
     this.requestPipeline = requestPipeline;
     this.contentRewriterRegistry = contentRewriterRegistry;
-       this.feedProcessorProvider = feedProcessorProvider;
+    this.feedProcessorProvider = feedProcessorProvider;
   }
 
 

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java?rev=1074690&r1=1074689&r2=1074690&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
 Fri Feb 25 20:35:12 2011
@@ -84,7 +84,7 @@ public class MakeRequestHandler {
       Provider<FeedProcessor> feedProcessorProvider) {
     this.requestPipeline = requestPipeline;
     this.contentRewriterRegistry = contentRewriterRegistry;
-       this.feedProcessorProvider = feedProcessorProvider;
+    this.feedProcessorProvider = feedProcessorProvider;
   }
 
   /**

Added: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCache.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCache.java?rev=1074690&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCache.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCache.java
 Fri Feb 25 20:35:12 2011
@@ -0,0 +1,69 @@
+/*
+ * 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.shindig.gadgets.servlet;
+
+import com.google.caja.plugin.Job;
+import com.google.caja.plugin.stages.JobCache;
+import com.google.caja.parser.ParseTreeNode;
+import com.google.caja.util.ContentType;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+import org.apache.shindig.common.cache.Cache;
+
+/**
+ * A per-module cache of intermediate cajoling results.
+ */
+final class ModuleCache extends JobCache {
+  private final Cache<ModuleCacheKey, ImmutableList<Job>> backingCache;
+
+  ModuleCache(Cache<ModuleCacheKey, ImmutableList<Job>> backingCache) {
+    this.backingCache = backingCache;
+  }
+
+  public ModuleCacheKey forJob(ContentType type, ParseTreeNode node) {
+    return new ModuleCacheKey(type, node);
+  }
+
+  public List<? extends Job> fetch(Key k) {
+    if (!(k instanceof ModuleCacheKey)) { return null; }
+    ImmutableList<Job> cachedJobs = backingCache.getElement((ModuleCacheKey) 
k);
+    if (cachedJobs == null) { return null; }
+    if (cachedJobs.isEmpty()) { return cachedJobs; }
+    return cloneJobs(cachedJobs);
+  }
+
+  public void store(Key k, List<? extends Job> derivatives) {
+    if (!(k instanceof ModuleCacheKey)) {
+      throw new IllegalArgumentException(k.getClass().getName());
+    }
+    ModuleCacheKey key = (ModuleCacheKey) k;
+    backingCache.addElement(key, cloneJobs(derivatives));
+  }
+
+  private static ImmutableList<Job> cloneJobs(Iterable<? extends Job> jobs) {
+    ImmutableList.Builder<Job> clones = ImmutableList.builder();
+    for (Job job : jobs) {
+      clones.add(job.clone());
+    }
+    return clones.build();
+  }
+
+}

Added: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKey.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKey.java?rev=1074690&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKey.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKey.java
 Fri Feb 25 20:35:12 2011
@@ -0,0 +1,190 @@
+/*
+ * 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.shindig.gadgets.servlet;
+
+import com.google.caja.parser.ParseTreeNode;
+import com.google.caja.plugin.stages.JobCache;
+import com.google.caja.util.ContentType;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NamedNodeMap;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A cryptographically strong hash of an abstract syntax tree.
+ */
+final class ModuleCacheKey implements JobCache.Key {
+  private final byte[] hashBytes;
+  private final int first32Bits;
+
+  ModuleCacheKey(ContentType type, ParseTreeNode node) {
+    Hasher hasher = new Hasher(type);
+    hasher.hash(node);
+    this.hashBytes = hasher.getHashBytes();
+    this.first32Bits = (hashBytes[0] & 0xff)
+        | ((hashBytes[1] & 0xff) << 8)
+        | ((hashBytes[2] & 0xff) << 16)
+        | ((hashBytes[3] & 0xff) << 24);
+  }
+
+  public ModuleCacheKeys asSingleton() {
+    return new ModuleCacheKeys(this);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return o instanceof ModuleCacheKey && Arrays.equals(hashBytes, 
((ModuleCacheKey) o).hashBytes);
+  }
+
+  @Override
+  public int hashCode() {
+    return first32Bits;
+  }
+
+
+  /** A helper that walks a tree to feed tree details to a hash fn. */
+  private static final class Hasher {
+    final MessageDigest md;
+    /** Buffer that captures output to allow md to amortize hashing. */
+    final byte[] buffer = new byte[1024];
+    /** Index of last byte in buffer that needs to be updated to md. */
+    int posInBuffer;
+
+    Hasher(ContentType t) {
+      try {
+        md = MessageDigest.getInstance("MD5");
+      } catch (NoSuchAlgorithmException ex) {
+        // We can't recover if a basic algorithm like MD5 is not supported.
+        throw (AssertionError) (new AssertionError().initCause(ex));
+      }
+      md.update((byte) t.ordinal());
+    }
+
+    /** Returns the hash of anything passed to {@link #hash(ParseTreeNode)}. */
+    byte[] getHashBytes() {
+      flushBuffer();
+      return md.digest();
+    }
+
+    /** Hashes the given parse tree. */
+    void hash(ParseTreeNode node) {
+      hash(System.identityHashCode(node.getClass()));
+
+      Object value = node.getValue();
+      if (value != null) {
+        if (value instanceof String) {
+          hash((String) value);
+        } else if (value instanceof Node) {
+          hash((Node) value);
+        } else {
+          hash(value.hashCode());
+        }
+      }
+
+      List<? extends ParseTreeNode> children = node.children();
+      hash((short) children.size());
+
+      for (ParseTreeNode child : children) {
+        hash(child);
+      }
+    }
+
+    private void hash(Node node) {
+      hash((short) node.getNodeType());
+      switch (node.getNodeType()) {
+        case Node.ATTRIBUTE_NODE:
+        case Node.ELEMENT_NODE:
+          hash(node.getNodeName());
+          break;
+        case Node.TEXT_NODE:
+        case Node.CDATA_SECTION_NODE:
+          hash(node.getNodeValue());
+          break;
+      }
+
+      hash((short) node.getChildNodes().getLength());
+
+      if (node.getNodeType() == Node.ELEMENT_NODE) {
+        NamedNodeMap attrs = node.getAttributes();
+        int nAttrs = attrs.getLength();
+        hash((short) nAttrs);
+        for (int i = 0; i < nAttrs; ++i) {
+          hash(attrs.item(i));
+        }
+      }
+
+      for (Node child = node.getFirstChild(); child != null;
+           child = child.getNextSibling()) {
+        hash(child);
+      }
+    }
+
+    private void hash(int n) {
+      requireSpaceInBuffer(4);
+      buffer[++posInBuffer] = (byte) ((n >> 24) & 0xff);
+      buffer[++posInBuffer] = (byte) ((n >> 16) & 0xff);
+      buffer[++posInBuffer] = (byte) ((n >> 8) & 0xff);
+      buffer[++posInBuffer] = (byte) (n & 0xff);
+    }
+
+    private void hash(short n) {
+      requireSpaceInBuffer(2);
+      buffer[++posInBuffer] = (byte) ((n >> 8) & 0xff);
+      buffer[++posInBuffer] = (byte) (n & 0xff);
+    }
+
+    private void hash(String text) {
+      int n = text.length();
+      for (int i = 0; i < n; ++i) {
+        char ch = text.charAt(i);
+        if (ch < 0x0080) {
+          requireSpaceInBuffer(1);
+          buffer[++posInBuffer] = (byte) ch;
+        } else if (ch < 0x080) {
+          requireSpaceInBuffer(2);
+          buffer[++posInBuffer] = (byte) (((ch >> 6) & 0x1f) | 0xc0);
+          buffer[++posInBuffer] = (byte) ((ch & 0x3f) | 0x80);
+        } else {
+          requireSpaceInBuffer(3);
+          buffer[++posInBuffer] = (byte) (((ch >> 12) & 0x0f) | 0xe0);
+          buffer[++posInBuffer] = (byte) (((ch >> 6) & 0x3f) | 0x80);
+          buffer[++posInBuffer] = (byte) ((ch & 0x3f) | 0x80);
+        }
+      }
+    }
+
+    /** Flushes the buffer if there is not enough space. */
+    private void requireSpaceInBuffer(int space) {
+      if (posInBuffer + space >= buffer.length) {
+        flushBuffer();
+      }
+    }
+
+    /** Writes the buffer content to the message digest. */
+    private void flushBuffer() {
+      md.update(buffer, 0, posInBuffer + 1);
+      posInBuffer = -1;  // Reset the buffer.
+    }
+  }
+}

Added: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKeys.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKeys.java?rev=1074690&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKeys.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ModuleCacheKeys.java
 Fri Feb 25 20:35:12 2011
@@ -0,0 +1,68 @@
+/*
+ * 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.shindig.gadgets.servlet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import com.google.caja.plugin.stages.JobCache;
+
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * A bundle of {@link ModuleCacheKey}s.
+ */
+final class ModuleCacheKeys implements JobCache.Keys {
+
+  final ImmutableList<ModuleCacheKey> keys;
+  
+  ModuleCacheKeys(ModuleCacheKey key) {
+    this.keys = ImmutableList.of(key);
+  }
+
+  private ModuleCacheKeys(Iterable<? extends ModuleCacheKey> keys) {
+    this.keys = ImmutableList.copyOf(keys);
+  }
+
+  public ModuleCacheKeys union(JobCache.Keys other) {
+    if (!other.iterator().hasNext()) { return this; }
+    ModuleCacheKeys that = (ModuleCacheKeys) other;
+    Set<ModuleCacheKey> allKeys = Sets.newLinkedHashSet();
+    allKeys.addAll(this.keys);
+    allKeys.addAll(that.keys);
+    if (allKeys.size() == this.keys.size()) { return this; }
+    if (allKeys.size() == that.keys.size()) { return that; }
+    return new ModuleCacheKeys(allKeys);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return o instanceof ModuleCacheKeys && keys.equals(((ModuleCacheKeys) 
o).keys);
+  }
+
+  @Override
+  public int hashCode() {
+    return keys.hashCode();
+  }
+
+  public Iterator<JobCache.Key> iterator() {
+    return ImmutableList.<JobCache.Key>copyOf(keys).iterator();
+  }
+
+}

Added: 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ModuleCacheTest.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ModuleCacheTest.java?rev=1074690&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ModuleCacheTest.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ModuleCacheTest.java
 Fri Feb 25 20:35:12 2011
@@ -0,0 +1,119 @@
+/*
+ * 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.shindig.gadgets.servlet;
+
+import com.google.caja.lexer.CharProducer;
+import com.google.caja.lexer.InputSource;
+import com.google.caja.parser.ParseTreeNode;
+import com.google.caja.reporting.EchoingMessageQueue;
+import com.google.caja.reporting.MessageContext;
+import com.google.caja.reporting.MessageQueue;
+import com.google.caja.util.ContentType;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import java.io.PrintWriter;
+import java.net.URI;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests equality of ModuleCacheKey{,s} instances.
+ */
+public class ModuleCacheTest extends TestCase {
+
+  public final void testKeyEqualToKeyFromSameNode() throws Exception {
+    assertKeyEquality(true, key("42"), key("42"));
+    assertKeyEquality(true, key("<br>"), key("<br>"));
+  }
+
+  public final void testKeysDifferBasedOnContentType() throws Exception {
+    assertKeysDiffer(key("foo", true), key("foo", false));
+  }
+
+  public final void testKeysDifferBasedOnNodeType() throws Exception {
+    assertKeysDiffer(key("foo"), key("'foo'"));
+    assertKeysDiffer(key("<div>"), key("div", true));
+  }
+
+  public final void testKeysDifferBasedOnNodeValue() throws Exception {
+    assertKeysDiffer(key("'foo'"), key("'bar'"));
+    assertKeysDiffer(key("break foo"), key("break"));
+    assertKeysDiffer(key("break"), key("break foo"));
+  }
+
+  public final void testKeysDifferBasedOnChildren() throws Exception {
+    assertKeysDiffer(key("return"), key("return 42"));
+  }
+
+  public final void testKeysDifferBasedOnElementName() throws Exception {
+    assertKeysDiffer(key("<div>"), key("<span>"));
+  }
+
+  public final void testKeysDifferBasedOnAttributeName() throws Exception {
+    assertKeysDiffer(key("<input type=text>"), key("<input name=text>"));
+  }
+
+  public final void testKeysDifferBasedOnAttributeValue() throws Exception {
+    assertKeysDiffer(key("<input type=text>"), key("<input type=checkbox>"));
+  }
+
+  public final void testKeysDifferBasedOnText() throws Exception {
+    assertKeysDiffer(key("<div>foo</div>"), key("<div>bar</div>"));
+  }
+
+  public final void testKeysDifferBasedOnCdataSection() throws Exception {
+    assertKeysDiffer(key("<?xml version=\"1.0\"?><div><![CDATA[foo]]></div>"),
+                     key("<?xml version=\"1.0\"?><div><![CDATA[bar]]></div>"));
+  }
+
+  private static void assertKeyEquality(
+      boolean equal, ModuleCacheKey k, ModuleCacheKey j) {
+    assertEquality(equal, k, j);
+    assertEquality(equal, k.asSingleton(), j.asSingleton());
+  }
+
+  private static void assertEquality(boolean equal, Object a, Object b) {
+    assertEquals(equal, a.equals(b));
+    if (equal) {
+      assertEquals(a.hashCode(), b.hashCode());
+    }
+  }
+
+  private static void assertKeysDiffer(ModuleCacheKey k, ModuleCacheKey j) {
+    assertKeyEquality(false, k, j);
+  }
+
+  private ModuleCacheKey key(String codeSnippet) throws Exception {
+    boolean isHtml = codeSnippet.trim().startsWith("<");
+    return key(codeSnippet, isHtml);
+  }
+
+  private ModuleCacheKey key(String codeSnippet, boolean isHtml) throws 
Exception {
+    MessageQueue mq = new EchoingMessageQueue(
+        new PrintWriter(System.err, true), new MessageContext());
+    InputSource is = new InputSource(new URI("test:///" + getName()));
+    ParseTreeNode node = CajaContentRewriter.parse(
+        is, CharProducer.Factory.fromString(codeSnippet, is),
+        isHtml ? "text/html" : "text/javascript",
+        mq);
+    return new ModuleCacheKey(isHtml ? ContentType.HTML : ContentType.JS, 
node);
+  }
+}

Modified: shindig/trunk/pom.xml
URL: 
http://svn.apache.org/viewvc/shindig/trunk/pom.xml?rev=1074690&r1=1074689&r2=1074690&view=diff
==============================================================================
--- shindig/trunk/pom.xml (original)
+++ shindig/trunk/pom.xml Fri Feb 25 20:35:12 2011
@@ -1529,7 +1529,7 @@
      <dependency>
        <groupId>caja</groupId>
        <artifactId>caja</artifactId>
-       <version>r4314</version>
+       <version>r4374</version>
        <scope>compile</scope>
        <exclusions>
          <!-- force use of xml-apis until caja fixes their pom -->


Reply via email to