Author: etnu
Date: Sun Sep 21 17:43:06 2008
New Revision: 697645

URL: http://svn.apache.org/viewvc?rev=697645&view=rev
Log:
Final parts of rendering pipeline changes.

The next step is replacing the old pipeline.

Modified:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingContentRewriter.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetRenderingTask.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistryTest.java

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java
 Sun Sep 21 17:43:06 2008
@@ -17,10 +17,10 @@
  */
 package org.apache.shindig.gadgets;
 
-import com.google.common.collect.Maps;
-
 import org.apache.shindig.common.ContainerConfig;
 
+import com.google.common.collect.Maps;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -115,7 +115,7 @@
    * Simplified ctor that registers a set of libraries for all contexts and
    * the default container. Used for testing.
    */
-  GadgetFeature(String name, List<JsLibrary> libraries,
+  public GadgetFeature(String name, List<JsLibrary> libraries,
       Collection<String> dependencies) {
     this.name = name;
     this.libraries = Maps.newEnumMap(RenderingContext.class);

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingContentRewriter.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingContentRewriter.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingContentRewriter.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderingContentRewriter.java
 Sun Sep 21 17:43:06 2008
@@ -18,22 +18,42 @@
  */
 package org.apache.shindig.gadgets.render;
 
+import org.apache.shindig.auth.SecurityToken;
+import org.apache.shindig.common.ContainerConfig;
 import org.apache.shindig.gadgets.Gadget;
 import org.apache.shindig.gadgets.GadgetContext;
 import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.GadgetFeature;
+import org.apache.shindig.gadgets.GadgetFeatureRegistry;
+import org.apache.shindig.gadgets.JsLibrary;
 import org.apache.shindig.gadgets.MessageBundleFactory;
 import org.apache.shindig.gadgets.MutableContent;
+import org.apache.shindig.gadgets.RenderingContext;
+import org.apache.shindig.gadgets.UnsupportedFeatureException;
+import org.apache.shindig.gadgets.UrlGenerator;
 import org.apache.shindig.gadgets.http.HttpRequest;
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.rewrite.ContentRewriter;
 import org.apache.shindig.gadgets.rewrite.RewriterResults;
+import org.apache.shindig.gadgets.spec.Feature;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
 import org.apache.shindig.gadgets.spec.MessageBundle;
+import org.apache.shindig.gadgets.spec.ModulePrefs;
 
+import com.google.common.collect.Sets;
 import com.google.inject.Inject;
 
+import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * Produces a valid HTML document for the gadget output, automatically 
inserting appropriate HTML
  * document wrapper data as needed.
@@ -51,6 +71,12 @@
  * - html document normalization
  */
 public class RenderingContentRewriter implements ContentRewriter {
+  static final Pattern DOCUMENT_SPLIT_PATTERN = Pattern.compile(
+      "(.*)<head>(.*?)<\\/head>(?:.*)<body(.*?)>(.*?)<\\/body>(?:.*)", 
Pattern.DOTALL);
+  static final int BEFORE_HEAD_GROUP = 1;
+  static final int HEAD_GROUP = 2;
+  static final int BODY_ATTRIBUTES_GROUP = 3;
+  static final int BODY_GROUP = 4;
   static final String DEFAULT_HEAD_CONTENT =
       "<style type=\"text/css\">" +
       "body,td,div,span,p{font-family:arial,sans-serif;}" +
@@ -60,13 +86,22 @@
       "</style>";
 
   private final MessageBundleFactory messageBundleFactory;
+  private final ContainerConfig containerConfig;
+  private final GadgetFeatureRegistry featureRegistry;
+  private final UrlGenerator urlGenerator;
 
   /**
    * @param messageBundleFactory Used for injecting message bundles into 
gadget output.
    */
   @Inject
-  public RenderingContentRewriter(MessageBundleFactory messageBundleFactory) {
+  public RenderingContentRewriter(MessageBundleFactory messageBundleFactory,
+                                  ContainerConfig containerConfig,
+                                  GadgetFeatureRegistry featureRegistry,
+                                  UrlGenerator urlGenerator) {
     this.messageBundleFactory = messageBundleFactory;
+    this.containerConfig = containerConfig;
+    this.featureRegistry = featureRegistry;
+    this.urlGenerator = urlGenerator;
   }
 
   public RewriterResults rewrite(HttpRequest request, HttpResponse original,
@@ -77,7 +112,8 @@
   public RewriterResults rewrite(Gadget gadget) {
     try {
       GadgetContent content = createGadgetContent(gadget);
-      insertJavascriptLibraries(gadget, content);
+      injectFeatureLibraries(gadget, content);
+      injectOnLoadHandlers(content);
       injectMessageBundles(gadget, content);
       // TODO: Use preloads when RenderedGadget gets promoted to Gadget.
       finalizeDocument(gadget, content);
@@ -89,11 +125,174 @@
     }
   }
 
+  private void injectOnLoadHandlers(GadgetContent content) {
+    content.appendBody("<script>gadgets.util.runOnLoadHandlers();</script>");
+  }
+
   /**
    * Injects javascript libraries needed to satisfy feature dependencies.
    */
-  private void insertJavascriptLibraries(Gadget gadget, GadgetContent content) 
{
-    // TODO: Need to migrate UrlGenerator and HttpUtil.getJsConfig from 
servlet code.
+  private void injectFeatureLibraries(Gadget gadget, GadgetContent content) 
throws GadgetException {
+    // TODO: If there isn't any js in the document, we can skip this. 
Unfortunately, that means
+    // both script tags (easy to detect) and event handlers (much more 
complex).
+    GadgetContext context = gadget.getContext();
+    GadgetSpec spec = gadget.getSpec();
+    String forcedLibs = context.getParameter("libs");
+    Set<String> forced;
+    if (forcedLibs == null) {
+      forced = Sets.newHashSet();
+    } else {
+      forced = Sets.newHashSet(forcedLibs.split(":"));
+    }
+
+    String externFmt = "<script src=\"%s\"></script>";
+
+    // Forced libs are always done first.
+    if (!forced.isEmpty()) {
+      String jsUrl = urlGenerator.getBundledJsUrl(forced, context);
+      content.appendHead(String.format(externFmt, jsUrl));
+      // Forced transitive deps need to be added as well so that they don't 
get pulled in twice.
+      // TODO: Figure out a clean way to avoid having to call getFeatures 
twice.
+      for (GadgetFeature dep : featureRegistry.getFeatures(forced)) {
+        forced.add(dep.getName());
+      }
+    }
+
+    StringBuilder inlineJs = new StringBuilder();
+
+    // Inline any libs that weren't forced. The ugly context switch between 
inline and external
+    // Js is needed to allow both inline and external scripts declared in 
feature.xml.
+    String container = context.getContainer();
+    Collection<GadgetFeature> features = getFeatures(spec, forced);
+
+    for (GadgetFeature feature : features) {
+      for (JsLibrary library : feature.getJsLibraries(RenderingContext.GADGET, 
container)) {
+        if (library.getType().equals(JsLibrary.Type.URL)) {
+          if (inlineJs.length() > 0) {
+            content.appendHead("<script>")
+                   .appendHead(inlineJs)
+                   .appendHead("</script>");
+            inlineJs.setLength(0);
+          }
+          content.appendHead(String.format(externFmt, library.getContent()));
+        } else {
+          if (!forced.contains(feature.getName())) {
+            // already pulled this file in from the shared contents.
+            if (context.getDebug()) {
+              inlineJs.append(library.getDebugContent());
+            } else {
+              inlineJs.append(library.getContent());
+            }
+            inlineJs.append(";\n");
+          }
+        }
+      }
+    }
+
+    inlineJs.append(getLibraryConfig(gadget, features));
+
+    if (inlineJs.length() > 0) {
+      content.appendHead("<script>")
+             .appendHead(inlineJs)
+             .appendHead("</script>");
+    }
+  }
+
+  /**
+   * Get all features needed to satisfy this rendering request.
+   *
+   * @param forced Forced libraries; added in addition to those found in the 
spec. Defaults to
+   * "core".
+   */
+  private Collection<GadgetFeature> getFeatures(GadgetSpec spec, 
Collection<String> forced)
+      throws GadgetException {
+    Map<String, Feature> features = spec.getModulePrefs().getFeatures();
+    Set<String> libs = Sets.newHashSet(features.keySet());
+    if (!forced.isEmpty()) {
+      libs.addAll(forced);
+    }
+
+    Set<String> unsupported = new HashSet<String>();
+    Collection<GadgetFeature> feats = featureRegistry.getFeatures(libs, 
unsupported);
+
+    unsupported.removeAll(forced);
+
+    if (!unsupported.isEmpty()) {
+      // Remove non-required libs
+      for (String missing : unsupported) {
+        if (!features.get(missing).getRequired()) {
+          unsupported.remove(missing);
+        }
+      }
+
+      // Throw error with full list of unsupported libraries
+      if (!unsupported.isEmpty()) {
+        throw new UnsupportedFeatureException(unsupported.toString());
+      }
+    }
+    return feats;
+  }
+
+  /**
+   * Creates a set of all configuration needed to satisfy the requested 
feature set.
+   *
+   * Appends special configuration for gadgets.util.hasFeature and 
gadgets.util.getFeatureParams to
+   * the output js.
+   *
+   * This can't be handled via the normal configuration mechanism because it 
is something that
+   * varies per request.
+   *
+   * @param reqs The features needed to satisfy the request.
+   * @throws GadgetException If there is a problem with the gadget auth token
+   */
+  private String getLibraryConfig(Gadget gadget, Collection<GadgetFeature> 
reqs)
+      throws GadgetException {
+    GadgetContext context = gadget.getContext();
+
+    JSONObject features = 
containerConfig.getJsonObject(context.getContainer(), "gadgets.features");
+
+    try {
+      // Discard what we don't care about.
+      JSONObject config;
+      if (features == null) {
+        config = new JSONObject();
+      } else {
+        String[] properties = new String[reqs.size()];
+        int i = 0;
+        for (GadgetFeature feature : reqs) {
+          properties[i++] = feature.getName();
+        }
+        config = new JSONObject(features, properties);
+      }
+
+      // Add gadgets.util support. This is calculated dynamically based on 
request inputs.
+      ModulePrefs prefs = gadget.getSpec().getModulePrefs();
+      JSONObject featureMap = new JSONObject();
+
+      for (Feature feature : prefs.getFeatures().values()) {
+        featureMap.put(feature.getName(), feature.getParams());
+      }
+      config.put("core.util", featureMap);
+
+      // Add authentication token config
+      SecurityToken authToken = context.getToken();
+      if (authToken != null) {
+        JSONObject authConfig = new JSONObject();
+        String updatedToken = authToken.getUpdatedToken();
+        if (updatedToken != null) {
+          authConfig.put("authToken", updatedToken);
+        }
+        String trustedJson = authToken.getTrustedJson();
+        if (trustedJson != null) {
+          authConfig.put("trustedJson", trustedJson);
+        }
+        config.put("shindig.auth", authConfig);
+      }
+      return "gadgets.config.init(" + config.toString() + ");\n";
+    } catch (JSONException e) {
+      // Shouldn't be possible.
+      throw new RuntimeException(e);
+    }
   }
 
   /**
@@ -117,25 +316,49 @@
    */
   private GadgetContent createGadgetContent(Gadget gadget) {
     GadgetContent content = new GadgetContent();
+    String doc = gadget.getContent();
+    if (doc.contains("<html>") && doc.contains("</html>")) {
+      Matcher matcher = DOCUMENT_SPLIT_PATTERN.matcher(doc);
+      if (matcher.matches()) {
+        content.appendHead(matcher.group(BEFORE_HEAD_GROUP))
+               .appendHead("<head>")
+               .appendHead(matcher.group(HEAD_GROUP));
+
+        content.appendBody("</head>")
+               .appendBody(createBodyTag(gadget, 
matcher.group(BODY_ATTRIBUTES_GROUP)))
+               .appendBody(matcher.group(BODY_GROUP));
+
+        content.appendTail("</body></html>");
+      } else {
+        makeDefaultContent(gadget, content);
+      }
+    } else {
+      makeDefaultContent(gadget, content);
+    }
+    return content;
+  }
+
+  /**
+   * Inserts basic content for a gadget. Used when the content does not 
contain a valid html doc.
+   */
+  private void makeDefaultContent(Gadget gadget, GadgetContent content) {
     content.appendHead("<html><head>");
     content.appendHead(DEFAULT_HEAD_CONTENT);
     content.appendBody("</head>");
-    content.appendBody(createDefaultBody(gadget));
+    content.appendBody(createBodyTag(gadget, ""));
     content.appendBody(gadget.getContent());
     content.appendTail("</body></html>");
-    // TODO: Parse valid documents into 3 parts.
-    return content;
   }
 
   /**
    * Produces the default body tag, inserting language direction as needed.
    */
-  private String createDefaultBody(Gadget gadget) {
+  private String createBodyTag(Gadget gadget, String extra) {
     LocaleSpec localeSpec = gadget.getLocale();
     if (localeSpec == null) {
-      return "<body>";
+      return "<body" + extra + ">";
     } else {
-      return "<body dir='" + localeSpec.getLanguageDirection() + "'>";
+      return "<body" + extra + " dir='" + localeSpec.getLanguageDirection() + 
"'>";
     }
   }
 
@@ -151,17 +374,17 @@
     private final StringBuilder body = new StringBuilder();
     private final StringBuilder tail = new StringBuilder();
 
-    GadgetContent appendHead(String content) {
+    GadgetContent appendHead(CharSequence content) {
       head.append(content);
       return this;
     }
 
-    GadgetContent appendBody(String content) {
+    GadgetContent appendBody(CharSequence content) {
       body.append(content);
       return this;
     }
 
-    GadgetContent appendTail(String content) {
+    GadgetContent appendTail(CharSequence content) {
       tail.append(content);
       return this;
     }
@@ -179,13 +402,7 @@
 
     @Override
     public String toString() {
-      return new StringBuilder(head.length() + body.length() + tail.length())
-      .append(head)
-      .append("\n--BODY--\n")
-      .append(body)
-      .append("\n--TAIL--\n")
-      .append(tail)
-      .toString();
+      return assemble();
     }
   }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistry.java
 Sun Sep 21 17:43:06 2008
@@ -45,7 +45,7 @@
   private String rewritersKey;
 
   @Inject
-  public CachingContentRewriterRegistry(List<? extends ContentRewriter> 
rewriters,
+  public CachingContentRewriterRegistry(List<ContentRewriter> rewriters,
       GadgetHtmlParser htmlParser,
       CacheProvider cacheProvider,
       @Named("shindig.rewritten-content.cache.capacity")int capacity) {

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistry.java
 Sun Sep 21 17:43:06 2008
@@ -41,7 +41,7 @@
   protected final GadgetHtmlParser htmlParser;
 
   @Inject
-  public DefaultContentRewriterRegistry(List<? extends ContentRewriter> 
rewriters,
+  public DefaultContentRewriterRegistry(List<ContentRewriter> rewriters,
       GadgetHtmlParser htmlParser) {
     if (rewriters == null) {
       rewriters = Collections.emptyList();

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetRenderingTask.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetRenderingTask.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetRenderingTask.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetRenderingTask.java
 Sun Sep 21 17:43:06 2008
@@ -32,7 +32,7 @@
 import org.apache.shindig.gadgets.JsLibrary;
 import org.apache.shindig.gadgets.LockedDomainService;
 import org.apache.shindig.gadgets.MessageBundleFactory;
-import org.apache.shindig.gadgets.DefaultUrlGenerator;
+import org.apache.shindig.gadgets.UrlGenerator;
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.spec.Feature;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
@@ -94,7 +94,7 @@
 
   private final ContainerConfig containerConfig;
 
-  private final DefaultUrlGenerator urlGenerator;
+  private final UrlGenerator urlGenerator;
 
   private GadgetContext context;
 
@@ -561,7 +561,7 @@
       MessageBundleFactory messageBundleFactory,
       GadgetFeatureRegistry registry,
       ContainerConfig containerConfig,
-      DefaultUrlGenerator urlGenerator,
+      UrlGenerator urlGenerator,
       LockedDomainService lockedDomainService) {
     this.server = server;
     this.messageBundleFactory = messageBundleFactory;

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/RenderingContentRewriterTest.java
 Sun Sep 21 17:43:06 2008
@@ -18,26 +18,49 @@
  */
 package org.apache.shindig.gadgets.render;
 
+import static 
org.apache.shindig.gadgets.render.RenderingContentRewriter.BEFORE_HEAD_GROUP;
+import static 
org.apache.shindig.gadgets.render.RenderingContentRewriter.BODY_ATTRIBUTES_GROUP;
+import static 
org.apache.shindig.gadgets.render.RenderingContentRewriter.BODY_GROUP;
+import static 
org.apache.shindig.gadgets.render.RenderingContentRewriter.DEFAULT_HEAD_CONTENT;
+import static 
org.apache.shindig.gadgets.render.RenderingContentRewriter.DOCUMENT_SPLIT_PATTERN;
+import static 
org.apache.shindig.gadgets.render.RenderingContentRewriter.HEAD_GROUP;
+import static org.easymock.EasyMock.expect;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import org.apache.shindig.common.ContainerConfig;
 import org.apache.shindig.gadgets.Gadget;
 import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.GadgetFeature;
+import org.apache.shindig.gadgets.GadgetFeatureRegistry;
 import org.apache.shindig.gadgets.JsLibrary;
 import org.apache.shindig.gadgets.MessageBundleFactory;
+import org.apache.shindig.gadgets.UrlGenerator;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
 import org.apache.shindig.gadgets.spec.MessageBundle;
 
+import com.google.caja.util.Join;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
 import org.easymock.classextension.EasyMock;
 import org.easymock.classextension.IMocksControl;
 import org.json.JSONObject;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -46,13 +69,20 @@
  */
 public class RenderingContentRewriterTest {
   private static final String BODY_CONTENT = "Some body content";
-  private static final Pattern EXPECTED_DOCUMENT_PATTERN = Pattern.compile(
-      "(.*</head>)(.*</body>)(.*)", Pattern.DOTALL);
   private final IMocksControl control = EasyMock.createNiceControl();
-  private final ContainerConfig config = 
control.createMock(ContainerConfig.class);
   private final FakeMessageBundleFactory messageBundleFactory = new 
FakeMessageBundleFactory();
-  private final RenderingContentRewriter rewriter
-      = new RenderingContentRewriter(messageBundleFactory);
+  private final ContainerConfig config = 
control.createMock(ContainerConfig.class);
+  private final UrlGenerator urlGenerator = new FakeUrlGenerator();
+
+  private FakeGadgetFeatureRegistry featureRegistry;
+  private RenderingContentRewriter rewriter;
+
+  @Before
+  public void setUp() throws Exception {
+    featureRegistry = new FakeGadgetFeatureRegistry();
+    rewriter
+        = new RenderingContentRewriter(messageBundleFactory, config, 
featureRegistry, urlGenerator);
+  }
 
   @Test
   public void defaultOutput() throws Exception {
@@ -69,22 +99,79 @@
 
     assertEquals(0, rewriter.rewrite(gadget).getCacheTtl());
 
-    Matcher matcher = EXPECTED_DOCUMENT_PATTERN.matcher(gadget.getContent());
+    Matcher matcher = DOCUMENT_SPLIT_PATTERN.matcher(gadget.getContent());
     assertTrue("Output is not valid HTML.", matcher.matches());
-    assertTrue("Missing opening html tag", 
matcher.group(1).contains("<html>"));
+    assertTrue("Missing opening html tag", 
matcher.group(BEFORE_HEAD_GROUP).contains("<html>"));
     assertTrue("Default head content is missing.",
-        
matcher.group(1).contains(RenderingContentRewriter.DEFAULT_HEAD_CONTENT));
+        matcher.group(HEAD_GROUP).contains(DEFAULT_HEAD_CONTENT));
     // Not very accurate -- could have just been user prefs.
-    assertTrue("Default javascript not included.", 
matcher.group(1).contains("<script>"));
-    assertTrue("Original document not preserved.", 
matcher.group(2).contains(BODY_CONTENT));
-    assertTrue("Missing closing html tag.", 
matcher.group(3).contains("</html>"));
+    assertTrue("Default javascript not included.",
+        matcher.group(HEAD_GROUP).contains("<script>"));
+    assertTrue("Original document not preserved.",
+        matcher.group(BODY_GROUP).contains(BODY_CONTENT));
+    assertTrue("gadgets.util.runOnLoadHandlers not invoked.",
+        
matcher.group(BODY_GROUP).contains("gadgets.util.runOnLoadHandlers();"));
+  }
+
+  @Test
+  public void completeDocument() throws Exception {
+    String docType = "<![DOCTYPE html]>";
+    String head = "<script src='foo.js'></script><style 
type='text/css'>body{color:red;}</style>";
+    String bodyAttr = " onload='foo();'";
+    String body = "hello, world.";
+    String doc = new StringBuilder()
+        .append(docType)
+        .append("<html><head>")
+        .append(head)
+        .append("</head><body").append(bodyAttr).append(">")
+        .append(body)
+        .append("</body></html>")
+        .toString();
+    String gadgetXml =
+        "<Module><ModulePrefs title=''/>" +
+        "<Content type='html'><![CDATA[" + doc + "]]></Content>" +
+        "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+    GadgetContext context = new GadgetContext() {
+      @Override
+      public String getParameter(String name) {
+        if (name.equals("libs")) {
+          return "foo";
+        }
+        return null;
+      }
+    };
+
+    Gadget gadget = new Gadget(context, spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "does-not-matter");
+    control.replay();
+
+    assertEquals(0, rewriter.rewrite(gadget).getCacheTtl());
+
+    Matcher matcher = DOCUMENT_SPLIT_PATTERN.matcher(gadget.getContent());
+    assertTrue("Output is not valid HTML.", matcher.matches());
+    assertTrue("DOCTYPE not preserved", 
matcher.group(BEFORE_HEAD_GROUP).contains(docType));
+    assertTrue("Missing opening html tag", 
matcher.group(BEFORE_HEAD_GROUP).contains("<html>"));
+    assertTrue("Custom head content is missing.", 
matcher.group(HEAD_GROUP).contains(head));
+    assertTrue("Forced javascript not included.",
+        matcher.group(HEAD_GROUP).contains("<script src=\"/js/foo\">"));
+    assertTrue("Custom body attributes missing.",
+        matcher.group(BODY_ATTRIBUTES_GROUP).contains(bodyAttr));
+    assertTrue("Original document not preserved.",
+        matcher.group(BODY_GROUP).contains(body));
+    assertTrue("gadgets.util.runOnLoadHandlers not invoked.",
+        
matcher.group(BODY_GROUP).contains("gadgets.util.runOnLoadHandlers();"));
+
+    // Skipping other tests; code path should be the same for the rest.
   }
 
   @Test
   public void bidiSettings() throws Exception {
     String gadgetXml =
       "<Module><ModulePrefs title=''>" +
-      " <Locale language_direction='rtl'/>" +
+      "  <Locale language_direction='rtl'/>" +
       "</ModulePrefs>" +
       "<Content type='html'>" + BODY_CONTENT + "</Content>" +
       "</Module>";
@@ -101,11 +188,255 @@
         gadget.getContent().contains("<body dir='rtl'>"));
   }
 
+  private Set<String> getInjectedScript(Gadget gadget) {
+    Pattern featurePattern
+        = Pattern.compile("(?:.*)<script 
src=\"\\/js\\/(.*?)\"><\\/script>(?:.*)", Pattern.DOTALL);
+    Matcher matcher = featurePattern.matcher(gadget.getContent());
+
+    assertTrue("Forced scripts not injected.", matcher.matches());
+
+    return Sets.newHashSet(matcher.group(1).split(":"));
+  }
+
+  @Test
+  public void forcedFeaturesInjectedExternal() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+
+    final Collection<String> libs = Arrays.asList("foo", "bar", "baz");
+    GadgetContext context = new GadgetContext() {
+      @Override
+      public String getParameter(String name) {
+        if (name.equals("libs")) {
+          return Join.join(":", libs);
+        }
+        return null;
+      }
+    };
+
+    Gadget gadget = new Gadget(context, spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "does-not-matter");
+    featureRegistry.addInline("bar", "does-not-matter");
+    featureRegistry.addInline("baz", "does-not-matter");
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    Set<String> actual = getInjectedScript(gadget);
+    Set<String> expected = Sets.immutableSortedSet("foo", "bar", "baz");
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void inlinedFeaturesWhenNothingForced() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+
+    Gadget gadget
+        = new Gadget(new GadgetContext(), spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "foo_content();");
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    assertTrue("Requested scripts not inlined.", 
gadget.getContent().contains("foo_content();"));
+  }
+
+  @Test
+  public void mixedExternalAndInline() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+
+    final Collection<String> libs = Arrays.asList("bar", "baz");
+    GadgetContext context = new GadgetContext() {
+      @Override
+      public String getParameter(String name) {
+        if (name.equals("libs")) {
+          return Join.join(":", libs);
+        }
+        return null;
+      }
+    };
+
+    Gadget gadget = new Gadget(context, spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "foo_content();");
+    featureRegistry.addInline("bar", "does-not-matter");
+    featureRegistry.addInline("baz", "does-not-matter");
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    Set<String> actual = getInjectedScript(gadget);
+    Set<String> expected = Sets.immutableSortedSet("bar", "baz");
+    assertEquals(expected, actual);
+    assertTrue("Requested scripts not inlined.", 
gadget.getContent().contains("foo_content();"));
+  }
+
+  @Test
+  public void urlFeaturesForcedExternal() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'/>" +
+      "  <Require feature='bar'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+
+    GadgetContext context = new GadgetContext() {
+      @Override
+      public String getParameter(String name) {
+        if (name.equals("libs")) {
+          return "baz";
+        }
+        return null;
+      }
+    };
+
+    Gadget gadget = new Gadget(context, spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "foo_content();");
+    featureRegistry.addExternal("bar", "http://example.org/external.js";);
+    featureRegistry.addInline("baz", "does-not-matter");
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    Set<String> actual = getInjectedScript(gadget);
+    Set<String> expected = Sets.immutableSortedSet("baz");
+    assertEquals(expected, actual);
+    assertTrue("Requested scripts not inlined.", 
gadget.getContent().contains("foo_content();"));
+    assertTrue("Forced external file not forced.",
+        gadget.getContent().contains("<script 
src=\"http://example.org/external.js\";>"));
+  }
+
+  private JSONObject getJson(Gadget gadget) throws Exception {
+    Pattern prefsPattern
+        = Pattern.compile("(?:.*)gadgets\\.config\\.init\\((.*)\\);(?:.*)", 
Pattern.DOTALL);
+    Matcher matcher = prefsPattern.matcher(gadget.getContent());
+    assertTrue("gadgets.config.init not invoked.", matcher.matches());
+    return new JSONObject(matcher.group(1));
+  }
+
+  @Test
+  public void featureConfigurationInjected() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+    Gadget gadget
+        = new Gadget(new GadgetContext(), spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "");
+
+    JSONObject conf = new JSONObject();
+    conf.put("foo", "blah");
+    expect(config.getJsonObject("default", 
"gadgets.features")).andReturn(conf);
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    JSONObject json = getJson(gadget);
+    assertEquals("blah", json.get("foo"));
+  }
+
+  @Test
+  public void featureConfigurationForced() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+
+    GadgetContext context = new GadgetContext() {
+      @Override
+      public String getParameter(String name) {
+        if (name.equals("libs")) {
+          return "bar";
+        }
+        return null;
+      }
+    };
+
+    Gadget gadget = new Gadget(context, spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "");
+    featureRegistry.addInline("bar", "");
+    JSONObject conf = new JSONObject();
+    conf.put("foo", "blah")
+        .put("bar", "baz");
+    expect(config.getJsonObject("default", 
"gadgets.features")).andReturn(conf);
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    JSONObject json = getJson(gadget);
+    assertEquals("blah", json.get("foo"));
+    assertEquals("baz", json.get("bar"));
+  }
+
   @Test
-  public void jsConfigurationInjected() throws Exception {
-    // TODO
+  public void gadgetsUtilConfigInjected() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'>" +
+      "    <Param name='bar'>baz</Param>" +
+      "  </Require>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+    Gadget gadget
+        = new Gadget(new GadgetContext(), spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    featureRegistry.addInline("foo", "");
+    JSONObject conf = new JSONObject();
+    conf.put("foo", "blah");
+    expect(config.getJsonObject("default", 
"gadgets.features")).andReturn(conf);
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    JSONObject json = getJson(gadget);
+    assertEquals("blah", json.get("foo"));
+
+    JSONObject util = json.getJSONObject("core.util");
+    JSONObject foo = util.getJSONObject("foo");
+    assertEquals("baz", foo.get("bar"));
   }
 
+  // TODO: Test for auth token stuff.
+
   @Test
   public void userPrefsInitializationInjected() throws Exception {
     String gadgetXml =
@@ -115,7 +446,7 @@
       "    <msg name='two'>bar</msg>" +
       "  </Locale>" +
       "</ModulePrefs>" +
-      "<Content type='html'>" + BODY_CONTENT + "</Content>" +
+      "<Content type='html'/>" +
       "</Module>";
 
     GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
@@ -126,7 +457,8 @@
 
     rewriter.rewrite(gadget);
 
-    Pattern prefsPattern = 
Pattern.compile("(?:.*)gadgets\\.Prefs\\.setMessages_\\((.*)\\);(?:.*)");
+    Pattern prefsPattern
+        = 
Pattern.compile("(?:.*)gadgets\\.Prefs\\.setMessages_\\((.*)\\);(?:.*)", 
Pattern.DOTALL);
     Matcher matcher = prefsPattern.matcher(gadget.getContent());
     assertTrue("gadgets.Prefs.setMessages_ not invoked.", matcher.matches());
     JSONObject json = new JSONObject(matcher.group(1));
@@ -134,6 +466,44 @@
     assertEquals("bar", json.get("two"));
   }
 
+  @Test(expected = RuntimeException.class)
+  public void unsupportedFeatureThrows() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Require feature='foo'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+    Gadget gadget
+        = new Gadget(new GadgetContext(), spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    control.replay();
+
+    rewriter.rewrite(gadget);
+  }
+
+  @Test
+  public void unsupportedOptionalFeatureDoesNotThrow() throws Exception {
+    String gadgetXml =
+      "<Module><ModulePrefs title=''>" +
+      "  <Optional feature='foo'/>" +
+      "</ModulePrefs>" +
+      "<Content type='html'/>" +
+      "</Module>";
+
+    GadgetSpec spec = new GadgetSpec(URI.create("#"), gadgetXml);
+    Gadget gadget
+        = new Gadget(new GadgetContext(), spec, 
Collections.<JsLibrary>emptySet(), config, null);
+
+    control.replay();
+
+    rewriter.rewrite(gadget);
+
+    // rewrite will throw if the optional unsupported feature doesn't work.
+  }
+
   /**
    * Simple message bundle factory -- only honors inline bundles.
    */
@@ -146,4 +516,57 @@
       return spec.getModulePrefs().getLocale(locale).getMessageBundle();
     }
   }
+
+  private static class FakeUrlGenerator implements UrlGenerator {
+    public String getBundledJsParam(Collection<String> features, GadgetContext 
context) {
+      throw new UnsupportedOperationException();
+    }
+
+    public String getIframeUrl(Gadget gadget) {
+      throw new UnsupportedOperationException();
+    }
+
+    public String getBundledJsUrl(Collection<String> features, GadgetContext 
context) {
+      return "/js/" + Join.join(":", features);
+    }
+  }
+
+  private static class FakeGadgetFeatureRegistry extends GadgetFeatureRegistry 
{
+    private final Map<String, GadgetFeature> features = Maps.newHashMap();
+
+    public void addInline(String name, String content) throws GadgetException {
+      List<JsLibrary> libs = Lists.newArrayList();
+      libs.add(JsLibrary.create(JsLibrary.Type.INLINE, content, name, null));
+      features.put(name, new GadgetFeature(name, libs, null));
+    }
+
+    public void addExternal(String name, String content) throws 
GadgetException {
+      List<JsLibrary> libs = Lists.newArrayList();
+      libs.add(JsLibrary.create(JsLibrary.Type.URL, content, name, null));
+      features.put(name, new GadgetFeature(name, libs, null));
+    }
+
+    public FakeGadgetFeatureRegistry() throws GadgetException {
+      super(null, null);
+    }
+
+    @Override
+    public Collection<GadgetFeature> getFeatures(Collection<String> needed) {
+      return getFeatures(needed, new HashSet<String>());
+    }
+
+    @Override
+    public Collection<GadgetFeature> getFeatures(Collection<String> needed,
+        Collection<String> unsupported) {
+      List<GadgetFeature> out = Lists.newArrayList();
+      for (String name : needed) {
+        if (features.containsKey(name)) {
+          out.add(features.get(name));
+        } else {
+          unsupported.add(name);
+        }
+      }
+      return out;
+    }
+  }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CachingContentRewriterRegistryTest.java
 Sun Sep 21 17:43:06 2008
@@ -48,9 +48,11 @@
 public class CachingContentRewriterRegistryTest {
   private final List<CaptureRewriter> rewriters
       = Lists.newArrayList(new CaptureRewriter(), new 
ModifyingCaptureContentRewriter());
+  private final List<ContentRewriter> contentRewriters
+      = Lists.<ContentRewriter>newArrayList(rewriters);
   private final FakeCacheProvider provider = new FakeCacheProvider();
   private final ContentRewriterRegistry registry
-      = new CachingContentRewriterRegistry(rewriters, null, provider, 100);
+      = new CachingContentRewriterRegistry(contentRewriters, null, provider, 
100);
   private final IMocksControl control = EasyMock.createNiceControl();
   private final ContainerConfig config = 
control.createMock(ContainerConfig.class);
 
@@ -179,7 +181,7 @@
     // The new registry is created using one additional rewriter, but the same 
cache.
     rewriters.add(new CaptureRewriter());
     ContentRewriterRegistry newRegistry
-        = new CachingContentRewriterRegistry(rewriters, null, provider, 100);
+        = new CachingContentRewriterRegistry(contentRewriters, null, provider, 
100);
 
     newRegistry.rewriteHttpResponse(request, response);
 

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistryTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistryTest.java?rev=697645&r1=697644&r2=697645&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistryTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriterRegistryTest.java
 Sun Sep 21 17:43:06 2008
@@ -29,6 +29,8 @@
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 
+import com.google.common.collect.Lists;
+
 import org.easymock.classextension.EasyMock;
 import org.easymock.classextension.IMocksControl;
 import org.junit.Test;
@@ -41,8 +43,10 @@
 public class DefaultContentRewriterRegistryTest {
   private final List<CaptureRewriter> rewriters
       = Arrays.asList(new CaptureRewriter(), new CaptureRewriter());
+  private final List<ContentRewriter> contentRewriters
+      = Lists.<ContentRewriter>newArrayList(rewriters);
   private final ContentRewriterRegistry registry
-      = new DefaultContentRewriterRegistry(rewriters, null);
+      = new DefaultContentRewriterRegistry(contentRewriters, null);
   private final IMocksControl control = EasyMock.createNiceControl();
   private final ContainerConfig config = 
control.createMock(ContainerConfig.class);
 


Reply via email to