You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by lr...@apache.org on 2008/05/20 01:05:00 UTC

svn commit: r658031 - in /incubator/shindig/trunk/java: gadgets/conf/ gadgets/src/main/java/org/apache/shindig/gadgets/ gadgets/src/main/java/org/apache/shindig/gadgets/http/ gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ gadgets/src/main/ja...

Author: lryan
Date: Mon May 19 16:04:59 2008
New Revision: 658031

URL: http://svn.apache.org/viewvc?rev=658031&view=rev
Log:
Initial content-rewrite implementation

Added:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CssRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlTagTransformer.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/NoOpContentRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ProxyingLinkRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleTagRewriter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ConcatProxyServlet.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CssRewriterTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/HtmlRewriterTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMergerTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriterTest.java
Modified:
    incubator/shindig/trunk/java/gadgets/conf/gadgets.properties
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/BasicHttpFetcher.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/GadgetSpecTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java
    incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.full.xml
    incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.gadgets.xml
    incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml

Modified: incubator/shindig/trunk/java/gadgets/conf/gadgets.properties
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/conf/gadgets.properties?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/conf/gadgets.properties (original)
+++ incubator/shindig/trunk/java/gadgets/conf/gadgets.properties Mon May 19 16:04:59 2008
@@ -7,4 +7,5 @@
 signing.key-file=
 locked-domain.enabled=false
 locked-domain.embed-host=127.0.0.1:8080
+content-rewrite.enabled=true
 cache.capacity=10000

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java Mon May 19 16:04:59 2008
@@ -18,12 +18,16 @@
  */
 package org.apache.shindig.gadgets;
 
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
 import org.apache.shindig.gadgets.http.HttpFetcher;
 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.ContentRewriterFeature;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
-
-import com.google.inject.Inject;
+import org.apache.shindig.gadgets.spec.View;
 
 import java.net.URI;
 
@@ -32,7 +36,9 @@
  */
 public class BasicGadgetSpecFactory implements GadgetSpecFactory {
 
-  private HttpFetcher specFetcher;
+  private final HttpFetcher specFetcher;
+  private final ContentRewriter rewriter;
+  private final boolean enableRewrite;
 
   public GadgetSpec getGadgetSpec(GadgetContext context)
       throws GadgetException {
@@ -52,11 +58,23 @@
     }
     GadgetSpec spec
         = new GadgetSpec(gadgetUri, response.getResponseAsString());
+    if (new ContentRewriterFeature(spec, enableRewrite).isRewriteEnabled()) {
+      for (View v : spec.getViews().values()) {
+        if (v.getType() == View.ContentType.HTML && rewriter != null) {
+          v.setRewrittenContent(
+              rewriter.rewrite(gadgetUri, v.getContent(), "text/html"));
+        }
+      }
+    }
     return spec;
   }
 
   @Inject
-  public BasicGadgetSpecFactory(HttpFetcher specFetcher) {
+  public BasicGadgetSpecFactory(HttpFetcher specFetcher,
+      ContentRewriter rewriter,
+      @Named("content-rewrite.enabled") String defaultEnableRewrite) {
     this.specFetcher = specFetcher;
+    this.rewriter = rewriter;
+    this.enableRewrite = Boolean.parseBoolean(defaultEnableRewrite);
   }
-}
+}
\ No newline at end of file

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java Mon May 19 16:04:59 2008
@@ -18,6 +18,12 @@
  */
 package org.apache.shindig.gadgets;
 
+import com.google.inject.AbstractModule;
+import com.google.inject.CreationException;
+import com.google.inject.Scopes;
+import com.google.inject.name.Names;
+import com.google.inject.spi.Message;
+
 import org.apache.shindig.common.util.ResourceLoader;
 import org.apache.shindig.gadgets.http.BasicHttpCache;
 import org.apache.shindig.gadgets.http.BasicHttpFetcher;
@@ -26,12 +32,8 @@
 import org.apache.shindig.gadgets.http.HttpFetcher;
 import org.apache.shindig.gadgets.http.RemoteContentFetcherFactory;
 import org.apache.shindig.gadgets.oauth.OAuthFetcherFactory;
-
-import com.google.inject.AbstractModule;
-import com.google.inject.CreationException;
-import com.google.inject.Scopes;
-import com.google.inject.name.Names;
-import com.google.inject.spi.Message;
+import org.apache.shindig.gadgets.rewrite.ContentRewriter;
+import org.apache.shindig.gadgets.rewrite.DefaultContentRewriter;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -52,6 +54,8 @@
   protected void configure() {
     Names.bindProperties(this.binder(), properties);
 
+    bind(ContentRewriter.class).to(DefaultContentRewriter.class);
+
     bind(HttpFetcher.class).to(BasicHttpFetcher.class);
     bind(HttpCache.class).to(BasicHttpCache.class);
 

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java Mon May 19 16:04:59 2008
@@ -17,6 +17,8 @@
  */
 package org.apache.shindig.gadgets;
 
+import com.google.inject.Inject;
+
 import org.apache.shindig.gadgets.http.ContentFetcherFactory;
 import org.apache.shindig.gadgets.http.HttpRequest;
 import org.apache.shindig.gadgets.http.HttpResponse;
@@ -27,8 +29,6 @@
 import org.apache.shindig.gadgets.spec.MessageBundle;
 import org.apache.shindig.gadgets.spec.Preload;
 
-import com.google.inject.Inject;
-
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -132,7 +132,7 @@
         Integer.toString(context.getModuleId()));
     UserPrefSubstituter.addSubstitutions(
         substituter, spec, context.getUserPrefs());
-    spec = spec.substitute(substituter);
+    spec = spec.substitute(substituter, !context.getIgnoreCache());
 
     Set<GadgetFeatureRegistry.Entry> features = getFeatures(spec);
 

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/BasicHttpFetcher.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/BasicHttpFetcher.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/BasicHttpFetcher.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/BasicHttpFetcher.java Mon May 19 16:04:59 2008
@@ -17,11 +17,11 @@
  */
 package org.apache.shindig.gadgets.http;
 
-import org.apache.shindig.common.util.InputStreamConsumer;
-
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.apache.shindig.common.util.InputStreamConsumer;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -137,7 +137,14 @@
   /** {@inheritDoc} */
   public HttpResponse fetch(HttpRequest request) {
     HttpResponse response = cache.getResponse(request);
-    if (response != null) return response;
+    // TODO - Make this sensitive to custom rewriting rules
+    if (response != null) {
+      if (request.getOptions().rewriter != null &&
+          response.getRewritten() != null) {
+        return response.getRewritten();
+      }
+      return response;
+    }
     try {
       URLConnection fetcher = getConnection(request);
       if ("POST".equals(request.getMethod()) &&
@@ -152,6 +159,11 @@
                                  fetcher.getOutputStream());
       }
       response = makeResponse(fetcher);
+      if (request.getOptions().rewriter != null) {
+        // TODO - Make this sensitive to different rewriting rules
+        response.setRewritten(
+            request.getOptions().rewriter.rewrite(request.getUri(), response));
+      }
       cache.addResponse(request, response);
       return response;
     } catch (IOException e) {

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java Mon May 19 16:04:59 2008
@@ -19,6 +19,8 @@
 
 package org.apache.shindig.gadgets.http;
 
+import org.apache.shindig.gadgets.rewrite.ContentRewriter;
+
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
@@ -355,6 +357,7 @@
     public boolean ignoreCache = false;
     public boolean ownerSigned = true;
     public boolean viewerSigned = true;
+    public ContentRewriter rewriter = null;
 
     public Options() {}
 
@@ -365,6 +368,7 @@
       this.ignoreCache = copyFrom.ignoreCache;
       this.ownerSigned = copyFrom.ownerSigned;
       this.viewerSigned = copyFrom.viewerSigned;
+      this.rewriter = copyFrom.rewriter;
     }
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java Mon May 19 16:04:59 2008
@@ -51,6 +51,8 @@
   private final Map<String, List<String>> headers;
   private final Map<String, String> metadata;
 
+  private HttpResponse rewritten;
+
   /**
    * Create a dummy empty map. Access via HttpResponse.ERROR
    */
@@ -134,6 +136,13 @@
   }
 
   /**
+   * @reutrn the content length
+   */
+  public int getContentLength() {
+    return responseBytes.length;
+  }
+
+  /**
    * @return An input stream suitable for reading the entirety of the response.
    */
   public InputStream getResponse() {
@@ -204,4 +213,20 @@
   public Map<String, String> getMetadata() {
     return this.metadata;
   }
+
+  /**
+   * Get the rewritten version of this content
+   * @return A rewritten HttpResponse
+   */
+  public HttpResponse getRewritten() {
+    return rewritten;
+  }
+
+  /**
+   * Set the rewritten version of this content
+   * @param rewritten
+   */
+  public void setRewritten(HttpResponse rewritten) {
+    this.rewritten = rewritten;
+  }
 }

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,64 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.http.HttpResponse;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+
+/**
+ * Standard interface for content rewriters
+ */
+public interface ContentRewriter {
+
+  /**
+   * Rewrite the original content located at source
+   * @param source   Location of the original content
+   * @param original Original content
+   * @return A rewritten copy of the original or null if no rewriting occurred
+   */
+  public HttpResponse rewrite(URI source, HttpResponse original);
+
+  /**
+   * Rewrite the original content located at source
+   * @param source   Location of the original content
+   * @param original Original content
+   * @param mimeType A string containing the mime type of the content, may
+   *                 contain other content as allowed in the HTTP Content-Type
+   *                 header
+   * @return A rewritten copy of the original or null if no rewriting occurred
+   */
+  public String rewrite(URI source, String original, String mimeType);
+
+  /**
+   * Rewrite the content in the original response located at source
+   * @param source   Location of the original content
+   * @param original Original content
+   * @param mimeType A string containing the mime type of the content, may
+   *                 contain other content as allowed in the HTTP Content-Type
+   *                 header
+   * @param rewritten Target of rewritten content, not written to if no
+   *                rewriting is done.
+   * @return true if rewrite occurred, false otherwise
+   */
+  public boolean rewrite(URI source, Reader original, String mimeType,
+      Writer rewritten);
+  
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ContentRewriterFeature.java Mon May 19 16:04:59 2008
@@ -0,0 +1,45 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.spec.Feature;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+
+/**
+ * Parser for the "proxy-rewrite" feature
+ */
+public class ContentRewriterFeature {
+
+  private boolean isEnabled;
+
+  public ContentRewriterFeature(GadgetSpec spec, boolean containerDefault) {
+    Feature f = spec.getModulePrefs().getFeatures().get("content-rewrite");
+    isEnabled = containerDefault;
+    if (f != null) {
+      if ("NONE".equalsIgnoreCase(f.getParams().get("include"))) {
+        isEnabled = false;
+      } else if ("ALL".equalsIgnoreCase(f.getParams().get("include"))) {
+        isEnabled = true;
+      }
+    }
+  }
+
+  public boolean isRewriteEnabled() {
+    return isEnabled;
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CssRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CssRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CssRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/CssRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,81 @@
+/*
+ * 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.rewrite;
+
+import com.google.caja.lexer.CharProducer;
+import com.google.caja.lexer.CssLexer;
+import com.google.caja.lexer.CssTokenType;
+import com.google.caja.lexer.InputSource;
+import com.google.caja.lexer.ParseException;
+import com.google.caja.lexer.Token;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Support rewriting links in CSS
+ */
+public class CssRewriter {
+
+  private static final Pattern urlMatcher =
+      Pattern.compile("(url\\s*\\(\\s*['\"]?)([^\\)'\"]*)(['\"]?\\s*\\))",
+        Pattern.CASE_INSENSITIVE);
+
+  public static String rewrite(String content, URI source,
+      LinkRewriter linkRewriter) {
+    StringWriter sw = new StringWriter((content.length() * 110) / 100);
+    rewrite(new StringReader(content), source, linkRewriter, sw);
+    return sw.toString();
+  }
+
+  public static void rewrite(Reader content, URI source,
+      LinkRewriter rewriter,
+      Writer writer) {
+    CharProducer producer = CharProducer.Factory.create(content,
+        new InputSource(source));
+    CssLexer lexer = new CssLexer(producer);
+    try {
+      while (lexer.hasNext()) {
+        Token<CssTokenType> token = lexer.next();
+        if (token.type == CssTokenType.URI) {
+          writer.write(rewriteLink(token, source, rewriter));
+          continue;
+        }
+        writer.write(token.text);
+      }
+    } catch (ParseException pe) {
+      pe.printStackTrace();
+    } catch (IOException ioe) {
+      ioe.printStackTrace();
+    }
+  }
+
+  private static String rewriteLink(Token<CssTokenType> token,
+      URI base, LinkRewriter rewriter) {
+    Matcher matcher = urlMatcher.matcher(token.text);
+    if (!matcher.find()) return token.text;
+    return "url(\"" + rewriter.rewrite(matcher.group(2), base) + "\")";
+  }
+}
+

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/DefaultContentRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,133 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.http.HttpResponse;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Default implementation of content rewriting.
+ */
+public class DefaultContentRewriter implements ContentRewriter {
+
+  public DefaultContentRewriter() {
+  }
+  
+  public HttpResponse rewrite(URI source, HttpResponse original) {
+    try {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream(
+          (original.getContentLength() * 110) / 100);
+      OutputStreamWriter output = new OutputStreamWriter(baos,
+          original.getEncoding());
+      if (rewrite(source,
+          new InputStreamReader(original.getResponse(), original.getEncoding()),
+          original.getHeader("Content-Type"),
+          output)) {
+        return new HttpResponse(original.getHttpStatusCode(),
+            baos.toByteArray(),
+            original.getAllHeaders());
+      }
+      return null;
+    } catch (UnsupportedEncodingException uee) {
+      throw new RuntimeException(uee);
+    }
+  }
+
+  public String rewrite(URI source, String original, String mimeType) {
+    StringWriter sw = new StringWriter();
+    if (rewrite(source, new StringReader(original), mimeType, sw)) {
+      return sw.toString();
+    } else {
+      return null;
+    }
+  }
+
+  public boolean rewrite(URI source, Reader r, String mimeType, Writer w) {
+    if (isHTML(mimeType)) {
+      Map<String, HtmlTagTransformer> transformerMap
+          = new HashMap<String, HtmlTagTransformer>();
+
+      if (getProxyUrl() != null) {
+        LinkRewriter linkRewriter = createLinkRewriter();
+        LinkingTagRewriter rewriter = new LinkingTagRewriter(
+            linkRewriter,
+            source);
+        for (String tag : rewriter.getSupportedTags()) {
+          transformerMap.put(tag, rewriter);
+        }
+        transformerMap.put("style", new StyleTagRewriter(source, linkRewriter));
+      }
+      if (getConcatUrl() != null) {
+        transformerMap
+            .put("script", new JavascriptTagMerger(getConcatUrl(), source));
+      }
+      HtmlRewriter.rewrite(r, source, transformerMap, w);
+      return true;
+    } else if (isCSS(mimeType)) {
+      if (getProxyUrl() != null) {
+        CssRewriter.rewrite(r, source, createLinkRewriter(), w);
+        return true;
+      } else {
+        return false;
+      }
+    }
+    return false;
+  }
+
+  private boolean isHTML(String mime) {
+    return (mime.toLowerCase().indexOf("html") != -1);
+  }
+
+  private boolean isCSS(String mime) {
+    return (mime.toLowerCase().indexOf("css") != -1);
+  }
+
+  private void copyContents(Reader r, Writer w) {
+    try {
+      for (int val = r.read(); val != -1; val = r.read()) {
+        w.write(val);
+      }
+    } catch (IOException ioe) {
+      throw new RuntimeException(ioe);
+    }
+  }
+
+  protected String getProxyUrl() {
+    return "/gadgets/proxy?url=";
+  }
+
+  protected String getConcatUrl() {
+    return "/gadgets/concat?";
+  }
+
+  protected LinkRewriter createLinkRewriter() {
+    return new ProxyingLinkRewriter(getProxyUrl());
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,126 @@
+/*
+ * 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.rewrite;
+
+import com.google.caja.lexer.CharProducer;
+import com.google.caja.lexer.HtmlLexer;
+import com.google.caja.lexer.HtmlTokenType;
+import com.google.caja.lexer.InputSource;
+import com.google.caja.lexer.ParseException;
+import com.google.caja.lexer.Token;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.util.Map;
+
+/**
+ * Rewrites an HTML content block
+ */
+public class HtmlRewriter {
+
+  private HtmlRewriter() {
+  }
+
+  public static String rewrite(String content, URI source,
+      Map<String, HtmlTagTransformer> transformers) {
+    StringWriter sw = new StringWriter((content.length() * 110) / 100);
+    rewrite(new StringReader(content), source, transformers, sw);
+    return sw.toString();
+  }
+
+  public static void rewrite(Reader content, URI source,
+      Map<String, HtmlTagTransformer> transformers,
+      Writer writer) {
+    CharProducer producer = CharProducer.Factory.create(content,
+        new InputSource(source));
+    HtmlLexer lexer = new HtmlLexer(producer);
+    try {
+      Token<HtmlTokenType> lastToken = null;
+      Token<HtmlTokenType> currentTag = null;
+      HtmlTagTransformer currentTransformer = null;
+      boolean tagChanged;
+      while (lexer.hasNext()) {
+        tagChanged = false;
+        Token<HtmlTokenType> token = lexer.next();
+        if (token.type == HtmlTokenType.IGNORABLE) {
+          continue;
+        }
+        if (token.type == HtmlTokenType.TAGBEGIN) {
+          currentTag = token;
+          tagChanged = true;
+        }
+        if (tagChanged) {
+          if (currentTransformer == null) {
+            currentTransformer = transformers
+                .get(currentTag.text.substring(1).toLowerCase());
+          } else {
+            if (!currentTransformer.acceptNextTag(currentTag)) {
+              writer.write(currentTransformer.close());
+              currentTransformer = transformers
+                  .get(currentTag.text.substring(1).toLowerCase());
+            }
+          }
+        }
+        if (currentTransformer == null) {
+          writer.write(producePreTokenSeparator(token, lastToken));
+          writer.write(token.text);
+          writer.write(producePostTokenSeparator(token, lastToken));
+        } else {
+          currentTransformer.accept(token, lastToken);
+        }
+        if (token.type == HtmlTokenType.TAGEND) {
+          currentTag = null;
+        }
+        lastToken = token;
+      }
+      if (currentTransformer != null) {
+        writer.write(currentTransformer.close());
+      }
+    } catch (ParseException pe) {
+      pe.printStackTrace();
+    } catch (IOException ioe) {
+      ioe.printStackTrace();
+    }
+  }
+
+
+  public static String producePreTokenSeparator(Token<HtmlTokenType> token,
+      Token<HtmlTokenType> lastToken) {
+    if (token.type == HtmlTokenType.ATTRNAME) {
+      return " ";
+    }
+    if (token.type == HtmlTokenType.ATTRVALUE &&
+        lastToken != null &&
+        lastToken.type == HtmlTokenType.ATTRNAME) {
+      return "=";
+    }
+    return "";
+  }
+
+
+  public static String producePostTokenSeparator(Token<HtmlTokenType> token,
+      Token<HtmlTokenType> lastToken) {
+    return "";
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlTagTransformer.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlTagTransformer.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlTagTransformer.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/HtmlTagTransformer.java Mon May 19 16:04:59 2008
@@ -0,0 +1,55 @@
+/*
+ * 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.rewrite;
+
+import com.google.caja.lexer.HtmlTokenType;
+import com.google.caja.lexer.Token;
+
+/**
+ * Provide custom transformations of a stream of HTML lexical tokens
+ * as provided by the Caja lexer. A tag transformer is initiated by
+ * the occurence of a start tag in the token stream. The transformer is
+ * fed all subsequent tokens within the tag. The transformer is given the\
+ * option of continuing to consume tokens from subsequent tags even after
+ * its initiating tag has ended.
+ */
+public interface HtmlTagTransformer {
+
+  /**
+   * Consume the token. Prior token supplied for context
+   * @param token current token
+   * @param lastToken 
+   */
+  public void accept(Token<HtmlTokenType> token,
+      Token<HtmlTokenType> lastToken);
+
+  /**
+   * A new tag has begun, should this transformer continue to process
+   * @param tagStart
+   * @return true if continuing to process the new tag
+   */
+  public boolean acceptNextTag(Token<HtmlTokenType> tagStart);
+
+  /**
+   * Close the transformer, reset its state and return any content
+   * for inclusion in the rewritten output
+   * @return transformed content
+   */
+  public String close();
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java Mon May 19 16:04:59 2008
@@ -0,0 +1,125 @@
+/*
+ * 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.rewrite;
+
+import com.google.caja.lexer.HtmlTokenType;
+import com.google.caja.lexer.Token;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Transform a contiguous block of script tags that refer to external scripts
+ * and rewrite them to a single script tag that uses a concatenating proxy
+ * to concatenate these scripts in order and also potentially perform other
+ * optimizations on the generated unified script such as minification
+ */
+public class JavascriptTagMerger implements HtmlTagTransformer {
+
+  private final List scripts = new ArrayList();
+
+  private final String concatBase;
+
+  private final URI relativeUrlBase;
+
+  private boolean isTagOpen = true;
+
+  /**
+   * @param concatBase Base url of the Concat servlet. Expected to be of the
+   *                   form www.host.com/concat?
+   * @param relativeUrlBase to resolve relative urls
+   */
+  public JavascriptTagMerger(String concatBase, URI relativeUrlBase) {
+    this.concatBase = concatBase;
+    this.relativeUrlBase = relativeUrlBase;
+  }
+
+  public void accept(Token<HtmlTokenType> token,
+      Token<HtmlTokenType> lastToken) {
+    try {
+      if (isTagOpen) {
+        if (lastToken != null &&
+            lastToken.type == HtmlTokenType.ATTRNAME &&
+            lastToken.text.equalsIgnoreCase("src")) {
+          scripts.add(new URI(stripQuotes(token.text)));
+        } else if (token.type == HtmlTokenType.UNESCAPED) {
+          scripts.add(token);
+        }
+      }
+    } catch (URISyntaxException use) {
+      throw new RuntimeException(use);
+    }
+  }
+
+  public boolean acceptNextTag(Token<HtmlTokenType> tagStart) {
+    if (tagStart.text.equalsIgnoreCase("<script")) {
+      isTagOpen = true;
+      return true;
+    } else if (tagStart.text.equalsIgnoreCase("</script")) {
+      isTagOpen = false;
+      return true;
+    }
+    return false;
+  }
+
+  public String close() {
+    List<URI> concat = new ArrayList<URI>();
+    StringBuilder builder = new StringBuilder(100);
+    for (Object o : scripts) {
+      if (o instanceof URI) {
+        concat.add((URI) o);
+      } else {
+        flushConcat(concat, builder);
+        builder.append("<script type=\"text/javascript\">")
+            .append(((Token<HtmlTokenType>) o).text).append("</script>");
+      }
+    }
+    flushConcat(concat, builder);
+    scripts.clear();
+    isTagOpen = true;
+    return builder.toString();
+  }
+
+  private void flushConcat(List<URI> concat, StringBuilder builder) {
+    if (concat.isEmpty()) {
+      return;
+    }
+    builder.append("<script src=\"").append(concatBase);
+    for (int i = 0; i < concat.size(); i++) {
+      URI srcUrl = concat.get(i);
+      if (!srcUrl.isAbsolute()) {
+        srcUrl = relativeUrlBase.resolve(srcUrl);
+      }
+      builder.append(i + 1).append("=")
+          .append(URLEncoder.encode(srcUrl.toString()));
+      if (i < concat.size() - 1) {
+        builder.append("&");
+      }
+    }
+    builder.append("\" type=\"text/javascript\"></script>");
+    concat.clear();
+  }
+
+  private String stripQuotes(String s) {
+    return s.replaceAll("\"", "").replaceAll("'","");
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,30 @@
+/*
+ * 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.rewrite;
+
+import java.net.URI;
+
+/**
+ * Rewrite a link
+ */
+public interface LinkRewriter {
+
+  public String rewrite(String link, URI context);
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,104 @@
+/*
+ * 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.rewrite;
+
+import com.google.caja.lexer.HtmlTokenType;
+import com.google.caja.lexer.Token;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Rewrite a linking attribute of an HTML tag to an arbitrary scheme */
+public class LinkingTagRewriter implements HtmlTagTransformer {
+
+  private final URI relativeBase;
+
+  private final LinkRewriter linkRewriter;
+
+  private final Map<String, Set<String>> tagAttributeTargets;
+
+  private final StringBuilder builder;
+
+  private Set<String> currentTagAttrs;
+
+  public static Map<String, Set<String>> getDefaultTargets() {
+    Map<String, Set<String>> targets  = new HashMap<String, Set<String>>();
+    targets.put("img", new HashSet(Arrays.asList("src")));
+    targets.put("embed", new HashSet(Arrays.asList("src")));
+    targets.put("link", new HashSet(Arrays.asList("href")));
+    return targets;
+  }
+
+  public LinkingTagRewriter(LinkRewriter linkRewriter, URI relativeBase) {
+    this(getDefaultTargets(), linkRewriter, relativeBase);
+  }
+
+  public LinkingTagRewriter(Map<String, Set<String>> tagAttributeTargets,
+      LinkRewriter linkRewriter, URI relativeBase) {
+    this.tagAttributeTargets = tagAttributeTargets;
+    this.linkRewriter = linkRewriter;
+    this.relativeBase = relativeBase;
+    builder = new StringBuilder(300);
+  }
+
+  public Set<String> getSupportedTags() {
+    return tagAttributeTargets.keySet();
+  }
+
+  public void accept(Token<HtmlTokenType> token,
+      Token<HtmlTokenType> lastToken) {
+    if (token.type == HtmlTokenType.TAGBEGIN) {
+      currentTagAttrs = tagAttributeTargets
+          .get(token.text.substring(1).toLowerCase());
+    }
+
+    if (currentTagAttrs != null &&
+        lastToken != null &&
+        lastToken.type == HtmlTokenType.ATTRNAME &&
+        currentTagAttrs.contains(lastToken.text.toLowerCase())) {
+      String link = stripQuotes(token.text);
+      builder.append("=\"");
+      builder.append(linkRewriter.rewrite(link, relativeBase));
+      builder.append("\"");
+      return;
+    }
+    builder.append(HtmlRewriter.producePreTokenSeparator(token, lastToken));
+    builder.append(token.text);
+    builder.append(HtmlRewriter.producePostTokenSeparator(token, lastToken));
+  }
+
+  public boolean acceptNextTag(Token<HtmlTokenType> tagStart) {
+    return false;
+  }
+
+  public String close() {
+    String result = builder.toString();
+    currentTagAttrs = null;
+    builder.setLength(0);
+    return result;
+  }
+
+  private String stripQuotes(String s) {
+    return s.replaceAll("\"", "").replaceAll("'", "");
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/NoOpContentRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/NoOpContentRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/NoOpContentRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/NoOpContentRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,47 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.http.HttpResponse;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+
+/**
+ *  A no-op content rewriter
+ */
+public class NoOpContentRewriter implements ContentRewriter {
+
+  public NoOpContentRewriter() {
+  }
+
+  public HttpResponse rewrite(URI source, HttpResponse original) {
+    return null;
+  }
+
+  public String rewrite(URI source, String original, String mimeType) {
+    return null;
+  }
+
+  public boolean rewrite(URI source, Reader original, String mimeType,
+      Writer rewritten) {
+    return false;
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ProxyingLinkRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ProxyingLinkRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ProxyingLinkRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/ProxyingLinkRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,23 @@
+package org.apache.shindig.gadgets.rewrite;
+
+import java.net.URI;
+import java.net.URLEncoder;
+
+/**
+ * Simple link rewriter that expect to rewrite a link to the form
+ * http://www.host.com/proxy/url=<url encoded link>
+ */
+public class ProxyingLinkRewriter implements LinkRewriter {
+
+  private final String prefix;
+
+  public ProxyingLinkRewriter(String prefix) {
+    this.prefix = prefix;
+  }
+
+  public String rewrite(String link, URI context) {
+    URI uri = context.resolve(link);
+    return prefix + URLEncoder.encode(uri.toString());
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleTagRewriter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleTagRewriter.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleTagRewriter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/StyleTagRewriter.java Mon May 19 16:04:59 2008
@@ -0,0 +1,43 @@
+package org.apache.shindig.gadgets.rewrite;
+
+import com.google.caja.lexer.HtmlTokenType;
+import com.google.caja.lexer.Token;
+
+import java.net.URI;
+
+/**
+ * Rewrite the CSS content of a style tag
+ */
+public class StyleTagRewriter implements HtmlTagTransformer {
+
+  private URI source;
+  private LinkRewriter linkRewriter;
+
+  private StringBuffer sb;
+
+  public StyleTagRewriter(URI source, LinkRewriter linkRewriter) {
+    this.source = source;
+    this.linkRewriter = linkRewriter;
+    sb = new StringBuffer(500);
+  }
+
+  public void accept(Token<HtmlTokenType> token, Token<HtmlTokenType> lastToken) {
+    if (token.type == HtmlTokenType.UNESCAPED) {
+      sb.append(CssRewriter.rewrite(token.text, source, linkRewriter));
+    } else {
+      sb.append(HtmlRewriter.producePreTokenSeparator(token, lastToken));
+      sb.append(token.text);
+      sb.append(HtmlRewriter.producePostTokenSeparator(token, lastToken));
+    }
+  }
+
+  public boolean acceptNextTag(Token<HtmlTokenType> tagStart) {
+    return false;
+  }
+
+  public String close() {
+    String result = sb.toString();
+    sb.setLength(0);
+    return result;
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ConcatProxyServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ConcatProxyServlet.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ConcatProxyServlet.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ConcatProxyServlet.java Mon May 19 16:04:59 2008
@@ -0,0 +1,100 @@
+/*
+ * 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.inject.Inject;
+
+import org.apache.shindig.common.servlet.InjectedServlet;
+import org.apache.shindig.gadgets.GadgetException;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet which concatenates the content of several proxied HTTP responses
+ *
+ * @see org.apache.shindig.gadgets.rewrite.JavascriptTagMerger
+ */
+public class ConcatProxyServlet extends InjectedServlet {
+
+  private static final Logger logger
+      = Logger.getLogger(ConcatProxyServlet.class.getName());
+
+  private ProxyHandler proxyHandler;
+
+  @Inject
+  public void setProxyHandler(ProxyHandler proxyHandler) {
+    this.proxyHandler = proxyHandler;
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws IOException {
+    for (int i = 1; i < Integer.MAX_VALUE; i++) {
+      String url = request.getParameter(Integer.toString(i));
+      if (url == null) {
+        break;
+      }
+      try {
+        proxyHandler.fetch(new RequestWrapper(request, url), response);
+      } catch (GadgetException ge) {
+        outputError(ge, response);
+      }
+    }
+  }
+
+
+  private void outputError(GadgetException excep, HttpServletResponse resp)
+      throws IOException {
+    StringBuilder err = new StringBuilder();
+    err.append(excep.getCode().toString());
+    err.append(' ');
+    err.append(excep.getMessage());
+
+    // Log the errors here for now. We might want different severity levels
+    // for different error codes.
+    logger.log(Level.INFO, "Concat proxy request failed", err);
+    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, err.toString());
+  }
+
+  /** Simple request wrapper to make repeated calls to ProxyHandler */
+  private class RequestWrapper extends HttpServletRequestWrapper {
+
+    private final String url;
+
+    private RequestWrapper(HttpServletRequest httpServletRequest, String url) {
+      super(httpServletRequest);
+      this.url = url;
+    }
+
+    @Override
+    public String getParameter(String paramName) {
+      if (ProxyHandler.URL_PARAM.equals(paramName)) {
+        return url;
+      }
+      return super.getParameter(paramName);
+    }
+  }
+}
+

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java Mon May 19 16:04:59 2008
@@ -18,22 +18,22 @@
  */
 package org.apache.shindig.gadgets.servlet;
 
+import com.google.inject.Inject;
+
 import org.apache.shindig.common.SecurityToken;
 import org.apache.shindig.common.SecurityTokenDecoder;
 import org.apache.shindig.common.SecurityTokenException;
 import org.apache.shindig.common.util.InputStreamConsumer;
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.LockedDomainService;
-import org.apache.shindig.gadgets.http.HttpFetcher;
 import org.apache.shindig.gadgets.http.ContentFetcherFactory;
-import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.http.HttpFetcher;
 import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.oauth.OAuthRequestParams;
+import org.apache.shindig.gadgets.rewrite.ContentRewriter;
 import org.apache.shindig.gadgets.spec.Auth;
 import org.apache.shindig.gadgets.spec.Preload;
-
-import com.google.inject.Inject;
-
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -89,6 +89,7 @@
   // This is a limitation of Guice, but this workaround...works.
   private ContentFetcherFactory contentFetcherFactory;
   private final LockedDomainService domainLocker;
+  private final ContentRewriter rewriter;
 
   @Inject
   public void setContentFetcher(ContentFetcherFactory contentFetcherFactory) {
@@ -98,10 +99,12 @@
   @Inject
   public ProxyHandler(ContentFetcherFactory contentFetcherFactory,
                       SecurityTokenDecoder securityTokenDecoder,
-                      LockedDomainService lockedDomainService) {
+                      LockedDomainService lockedDomainService,
+                      ContentRewriter rewriter) {
     this.contentFetcherFactory = contentFetcherFactory;
     this.securityTokenDecoder = securityTokenDecoder;
     this.domainLocker = lockedDomainService;
+    this.rewriter = rewriter;
   }
 
   /**
@@ -208,6 +211,7 @@
         options.ownerSigned = Boolean
             .parseBoolean(request.getParameter(SIGN_OWNER));
       }
+      options.rewriter = rewriter;
 
       return new HttpRequest(
           method, url, headers, postBody, options);

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/GadgetSpec.java Mon May 19 16:04:59 2008
@@ -21,7 +21,6 @@
 import org.apache.shindig.common.xml.XmlException;
 import org.apache.shindig.common.xml.XmlUtil;
 import org.apache.shindig.gadgets.Substitutions;
-
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
@@ -101,7 +100,7 @@
    * @param substituter
    * @return The substituted spec.
    */
-  public GadgetSpec substitute(Substitutions substituter) {
+  public GadgetSpec substitute(Substitutions substituter, boolean rewrite) {
     GadgetSpec spec = new GadgetSpec(this);
     spec.modulePrefs = modulePrefs.substitute(substituter);
     if (userPrefs.size() == 0) {
@@ -115,7 +114,7 @@
     }
     Map<String, View> viewMap = new HashMap<String, View>(views.size());
     for (View view : views.values()) {
-     viewMap.put(view.getName(), view.substitute(substituter));
+     viewMap.put(view.getName(), view.substitute(substituter, rewrite));
     }
     spec.views = Collections.unmodifiableMap(viewMap);
 

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java Mon May 19 16:04:59 2008
@@ -16,9 +16,9 @@
  * specific language governing permissions and limitations under the License.
  */
 package org.apache.shindig.gadgets.spec;
+
 import org.apache.shindig.common.xml.XmlUtil;
 import org.apache.shindig.gadgets.Substitutions;
-
 import org.w3c.dom.Element;
 
 import java.net.URI;
@@ -89,9 +89,13 @@
    * @param substituter
    * @return The substituted view.
    */
-  public View substitute(Substitutions substituter) {
+  public View substitute(Substitutions substituter, boolean rewrite) {
     View view = new View(this);
-    view.content = substituter.substituteString(null, content);
+    if (rewrite && rewrittenContent != null) {
+      view.content = substituter.substituteString(null, rewrittenContent);
+    } else {
+      view.content = substituter.substituteString(null, content);
+    }
     view.href = substituter.substituteUri(null, href);
     return view;
   }
@@ -180,4 +184,17 @@
       return "url".equals(value) ? URL : HTML;
     }
   }
+
+  //
+  // Decorations
+  //
+  private String rewrittenContent;
+
+  public String getRewrittenContent() {
+    return rewrittenContent;
+  }
+
+  public void setRewrittenContent(String rewrittenContent) {
+    this.rewrittenContent = rewrittenContent;
+  }
 }
\ No newline at end of file

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java Mon May 19 16:04:59 2008
@@ -22,6 +22,7 @@
 import org.apache.shindig.common.SecurityTokenDecoder;
 import org.apache.shindig.gadgets.http.ContentFetcherFactory;
 import org.apache.shindig.gadgets.http.HttpFetcher;
+import org.apache.shindig.gadgets.rewrite.NoOpContentRewriter;
 
 import java.util.concurrent.Executor;
 
@@ -39,7 +40,7 @@
   public final HttpFetcher fetcher = mock(HttpFetcher.class);
   public final GadgetBlacklist blacklist = mock(GadgetBlacklist.class);
   public final GadgetSpecFactory specFactory =
-      new BasicGadgetSpecFactory(fetcher);
+      new BasicGadgetSpecFactory(fetcher, new NoOpContentRewriter(), "true");
   public final MessageBundleFactory bundleFactory =
       new BasicMessageBundleFactory(fetcher);
   public GadgetFeatureRegistry registry;

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CssRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CssRewriterTest.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CssRewriterTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/CssRewriterTest.java Mon May 19 16:04:59 2008
@@ -0,0 +1,63 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.EasyMockTestCase;
+
+import java.net.URI;
+import java.util.Map;
+
+/**
+ * test CSS link rewriting
+ */
+public class CssRewriterTest  extends EasyMockTestCase {
+
+  private URI dummyUri;
+
+  private Map<String, HtmlTagTransformer> defaultTransformerMap;
+
+  private LinkRewriter defaultRewriter = new ProxyingLinkRewriter(
+      "http://www.test.com/proxy?url=");
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    dummyUri = new URI("http://www.w3c.org");
+    defaultRewriter = new ProxyingLinkRewriter("http://www.test.com/proxy?url=");
+  }
+
+  private void validateRewritten(String content, URI base,
+      LinkRewriter rewriter, String expected) {
+    assertEquals(expected, CssRewriter.rewrite(content, base, rewriter));
+  }
+
+  private void validateRewritten(String content, String expected) {
+    validateRewritten(content, dummyUri, defaultRewriter, expected);
+  }
+
+  public void testUrlDeclarationRewrite() {
+    String original =
+        "div {list-style-image:url('http://a.b.com/bullet.gif');list-style-position:outside;margin:5px;padding:0}\n" +
+         ".someid {background-image:url(http://a.b.com/bigimg.png);float:right;width:165px;height:23px;margin-top:4px;margin-left:5px}";
+    String rewritten =
+        "div {list-style-image:url(\"http://www.test.com/proxy?url=http%3A%2F%2Fa.b.com%2Fbullet.gif\");list-style-position:outside;margin:5px;padding:0}\n" +
+         ".someid {background-image:url(\"http://www.test.com/proxy?url=http%3A%2F%2Fa.b.com%2Fbigimg.png\");float:right;width:165px;height:23px;margin-top:4px;margin-left:5px}";
+    validateRewritten(original, rewritten);
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/HtmlRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/HtmlRewriterTest.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/HtmlRewriterTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/HtmlRewriterTest.java Mon May 19 16:04:59 2008
@@ -0,0 +1,108 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.EasyMockTestCase;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test the HTML rewriter foundation for basic operation
+ */
+public class HtmlRewriterTest extends EasyMockTestCase {
+
+  private URI dummyUri;
+
+  private Map<String, HtmlTagTransformer> defaultTransformerMap;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    dummyUri = new URI("http://www.w3c.org");
+    defaultTransformerMap = new HashMap<String, HtmlTagTransformer>();
+  }
+
+  private void validateRewritten(String content, URI base,
+      Map<String, HtmlTagTransformer> transformerMap,
+      String expected) {
+    assertEquals(expected, HtmlRewriter.rewrite(content, base, transformerMap));
+  }
+
+  private void validateRewritten(String content, String expected) {
+    validateRewritten(content, dummyUri, defaultTransformerMap, expected);
+  }
+
+  public void testPreserveJunk() {
+    String s = "<div id=notvalid name='horrorShow\" />\n"
+        + "</br>\n"
+        + "</div>";
+    validateRewritten(s, s);
+  }
+
+  public void testPreserveScript() {
+    String s = "<script src=\"http://a.b.com/1.js\">\n</script>"
+        + "\n<script src=\"http://a.b.com/2.js\">\n</script>";
+    validateRewritten(s, s);
+  }
+
+  public void testPreserveCss() {
+    String s = "<html><style>body { background-color:#7f7f7f }</style>";
+    validateRewritten(s, s);
+  }
+
+  public void testBigChunk() {
+    // The source of the goolge homepage at a point in time
+    String s =
+        "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n"
+            + "<html><head><title>DRAFT - HTML4 Test Suite:7_5_5-BF-01</title>\n"
+            + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n"
+            + "<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n"
+            + "<link rel=\"stylesheet\" type=\"text/css\" media=\"screen\" href=\"static.css\"></head>\n"
+            + "<body>\n"
+            + "<div class=\"navigation\">\n"
+            + "<h2>DRAFT - HTML4 Test Suite: Test 7_5_5-BF-01 Headings: The H1, H2,  H3, H4, H5, H6 elements</h2>\n"
+            + "<hr>[<a href=\"sec7_5_4-BF-02.html\">Previous</a>] [<a href=\"section8.html\">Next</a>] [<a href=\"section7.html\">Section</a>]"
+            + " [<a href=\"index.html\">Contents</a>] [<a href=\"http://www.w3.org/TR/html401/struct/global.html#h-7.5.5\">Specification</a>]<BR>\n"
+            + "\n"
+            + "</div>\n"
+            + "<object height=\"100%\" width=\"100%\" border=\"0\" type=\"text/html\" data=\"7_5_5-BF-01.html\">\n"
+            + "<a class=\"navigation\" href=\"7_5_5-BF-01.html\" target=\"testwindow\">Test</a></object>\n"
+            + "</body></html>";
+    validateRewritten(s, s);
+  }
+
+  public void testPreserveCData() {
+    String s = "<script><![CDATA[dontcare]]></script>";
+    validateRewritten(s, s);
+  }
+
+  public void testPreserveComment() {
+    String s = "<script>  <!-- something here --></script>";
+    validateRewritten(s, s);
+  }
+
+  // Eventaully we want the opposite.
+  public void testPreserveUselessWhitespace() {
+    String s = "          <script>         \n</script>";
+    validateRewritten(s, s);
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMergerTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMergerTest.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMergerTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMergerTest.java Mon May 19 16:04:59 2008
@@ -0,0 +1,139 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.EasyMockTestCase;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test for Javascript tag merge functionality
+ */
+public class JavascriptTagMergerTest extends EasyMockTestCase {
+
+  private URI dummyUri;
+  private URI relativeBase;
+
+  private Map<String, HtmlTagTransformer> defaultTransformerMap;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    dummyUri = new URI("http://www.w3c.org");
+    relativeBase = new URI("http://a.b.com/");
+    defaultTransformerMap = new HashMap<String, HtmlTagTransformer>();
+    defaultTransformerMap
+        .put("script", new JavascriptTagMerger("http://www.test.com/concat?",
+            relativeBase));
+  }
+
+  private void validateRewritten(String content, URI base,
+      Map<String, HtmlTagTransformer> transformerMap,
+      String expected) {
+    assertEquals(expected, HtmlRewriter.rewrite(content, base, transformerMap));
+  }
+
+  private void validateRewritten(String content, String expected) {
+    validateRewritten(content, dummyUri, defaultTransformerMap, expected);
+  }
+
+  public void testPreserveNoExternal() {
+    String original = "<script type=\"text/javascript\">\n"
+        + "doSomething\n"
+        + "</script>";
+    validateRewritten(original, original);
+  }
+
+  public void testPreserveNoScript() {
+    String original
+        = "<html><div id=\"test\">ceci ne pas une script</div></html>";
+    validateRewritten(original, original);
+  }
+
+  public void testPreserveWithComment() {
+    String original = "<script type=\"text/javascript\"><!--\n"
+        + "doSomething\n"
+        + "--></script>";
+    validateRewritten(original, original);
+  }
+
+  public void testSingleScriptReWrite() {
+    String original = "<script src=\"http://a.b.com/1.js\"></script>";
+    String rewritten
+        = "<script src=\"http://www.test.com/concat?1=http%3A%2F%2Fa.b.com%2F1.js\" type=\"text/javascript\"></script>";
+    validateRewritten(original, rewritten);
+  }
+
+  public void testTwoScriptReWrite() {
+    String original = "<script src=\"http://a.b.com/1.js\"></script>\n"
+        + "<script src=\"http://a.b.com/2.js\"></script>";
+    String rewritten
+        = "<script src=\"http://www.test.com/concat?1=http%3A%2F%2Fa.b.com%2F1.js&2=http%3A%2F%2Fa.b.com%2F2.js\" type=\"text/javascript\"></script>";
+    validateRewritten(original, rewritten);
+  }
+
+  public void testLeadAndTrailingScriptReWrite() {
+    String original = "<script>\n"
+        + "doSomething\n"
+        + "</script>\n"
+        + "<script src=\"http://a.b.com/1.js\"></script>\n"
+        + "<script src=\"http://a.b.com/2.js\"></script>\n"
+        + "<script>\n"
+        + "doSomething\n"
+        + "</script>";
+    String rewritten = "<script type=\"text/javascript\">\n"
+        + "doSomething\n"
+        + "</script>"
+        + "<script src=\"http://www.test.com/concat?1=http%3A%2F%2Fa.b.com%2F1.js&2=http%3A%2F%2Fa.b.com%2F2.js\" type=\"text/javascript\"></script>"
+        + "<script type=\"text/javascript\">\n"
+        + "doSomething\n"
+        + "</script>";
+    validateRewritten(original, rewritten);
+  }
+
+  public void testInterspersed() {
+    String original = "<script src=\"http://a.b.com/1.js\"></script>\n"
+        + "<script src=\"http://a.b.com/2.js\"></script>\n"
+        + "<script type=\"text/javascript\"><!-- doSomething --></script>\n"
+        + "<script src=\"http://a.b.com/3.js\"></script>\n"
+        + "<script src=\"http://a.b.com/4.js\"></script>";
+    String rewritten =
+        "<script src=\"http://www.test.com/concat?1=http%3A%2F%2Fa.b.com%2F1.js&2=http%3A%2F%2Fa.b.com%2F2.js\" type=\"text/javascript\"></script>"
+            + "<script type=\"text/javascript\"><!-- doSomething --></script>"
+            + "<script src=\"http://www.test.com/concat?1=http%3A%2F%2Fa.b.com%2F3.js&2=http%3A%2F%2Fa.b.com%2F4.js\" type=\"text/javascript\"></script>";
+    validateRewritten(original, rewritten);
+  }
+
+  public void testDerelativizeHostRelative() {
+    String original = "<script src=\"/1.js\"></script>";
+    String rewritten
+        = "<script src=\"http://www.test.com/concat?1=http%3A%2F%2Fa.b.com%2F1.js\" type=\"text/javascript\"></script>";
+    validateRewritten(original, rewritten);
+  }
+
+  public void testDerelativizePathRelative() {
+    String original = "<script src=\"1.js\"></script>";
+    String rewritten
+        = "<script src=\"http://www.test.com/concat?1=http%3A%2F%2Fa.b.com%2F1.js\" type=\"text/javascript\"></script>";
+    validateRewritten(original, rewritten);
+  }
+
+}

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriterTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriterTest.java?rev=658031&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriterTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/LinkingTagRewriterTest.java Mon May 19 16:04:59 2008
@@ -0,0 +1,74 @@
+/*
+ * 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.rewrite;
+
+import org.apache.shindig.gadgets.EasyMockTestCase;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test link rewriting 
+ */
+public class LinkingTagRewriterTest  extends EasyMockTestCase {
+
+  private URI dummyUri;
+  private URI relativeBase;
+
+  private Map<String, HtmlTagTransformer> defaultTransformerMap;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    dummyUri = new URI("http://www.w3c.org");
+    relativeBase = new URI("http://a.b.com/");
+    LinkingTagRewriter rewriter = new LinkingTagRewriter(
+        new ProxyingLinkRewriter("http://www.test.com/proxy?url="),
+          relativeBase);
+
+    defaultTransformerMap = new HashMap<String, HtmlTagTransformer>();
+    for (String tag : rewriter.getSupportedTags()) {
+      defaultTransformerMap
+        .put(tag, rewriter);
+    }
+  }
+
+  private void validateRewritten(String content, URI base,
+      Map<String, HtmlTagTransformer> transformerMap,
+      String expected) {
+    assertEquals(expected, HtmlRewriter.rewrite(content, base, transformerMap));
+  }
+
+  private void validateRewritten(String content, String expected) {
+    validateRewritten(content, dummyUri, defaultTransformerMap, expected);
+  }
+
+  public void testStandardRewrite() {
+    String original = "<img src=\"http://a.b.com/img.gif\"></img>\n"
+        + "<IMG src=\"http://a.b.com/img.gif\"/>\n"
+        + "<eMbeD src=\"http://a.b.com/some.mov\" width=\"100\" height=\"30px\"/>";
+    String expected = "<img src=\"http://www.test.com/proxy?url=http%3A%2F%2Fa.b.com%2Fimg.gif\"></img>\n"
+        + "<IMG src=\"http://www.test.com/proxy?url=http%3A%2F%2Fa.b.com%2Fimg.gif\"/>\n"
+        + "<eMbeD src=\"http://www.test.com/proxy?url=http%3A%2F%2Fa.b.com%2Fsome.mov\" width=\"100\" height=\"30px\"/>";
+    validateRewritten(original, expected);
+  }
+
+
+}

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java Mon May 19 16:04:59 2008
@@ -21,6 +21,8 @@
 import org.apache.shindig.gadgets.GadgetTestFixture;
 import org.apache.shindig.gadgets.LockedDomainService;
 import org.apache.shindig.gadgets.http.ContentFetcherFactory;
+import org.apache.shindig.gadgets.rewrite.ContentRewriter;
+import org.apache.shindig.gadgets.rewrite.NoOpContentRewriter;
 
 public abstract class HttpTestFixture extends GadgetTestFixture {
   public final ProxyHandler proxyHandler;
@@ -31,13 +33,15 @@
   public final UrlGenerator urlGenerator = mock(UrlGenerator.class);
   public final LockedDomainService lockedDomainService =
     mock(LockedDomainService.class);
+  public final ContentRewriter rewriter = new NoOpContentRewriter();
 
   public HttpTestFixture() {
     super();
     proxyHandler = new ProxyHandler(
         contentFetcherFactory,
         securityTokenDecoder,
-        lockedDomainService);
+        lockedDomainService,
+        rewriter);
     gadgetRenderer = new GadgetRenderingTask(gadgetServer, registry,
         containerConfig, urlGenerator, securityTokenDecoder, lockedDomainService);
     jsonRpcHandler = new JsonRpcHandler(executor, gadgetServer, urlGenerator);

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/GadgetSpecTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/GadgetSpecTest.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/GadgetSpecTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/GadgetSpecTest.java Mon May 19 16:04:59 2008
@@ -19,12 +19,12 @@
 
 package org.apache.shindig.gadgets.spec;
 
+import junit.framework.TestCase;
+
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.Substitutions;
 import org.apache.shindig.gadgets.Substitutions.Type;
 
-import junit.framework.TestCase;
-
 import java.net.URI;
 
 public class GadgetSpecTest extends TestCase {
@@ -104,7 +104,7 @@
     substituter.addSubstitution(Type.USER_PREF, "title", title);
     substituter.addSubstitution(Type.MESSAGE, "content", content);
 
-    GadgetSpec spec = new GadgetSpec(SPEC_URL, xml).substitute(substituter);
+    GadgetSpec spec = new GadgetSpec(SPEC_URL, xml).substitute(substituter, false);
     assertEquals(title, spec.getModulePrefs().getTitle());
     assertEquals(content, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
   }

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/spec/ViewTest.java Mon May 19 16:04:59 2008
@@ -19,12 +19,12 @@
 
 package org.apache.shindig.gadgets.spec;
 
+import junit.framework.TestCase;
+
 import org.apache.shindig.common.xml.XmlUtil;
 import org.apache.shindig.gadgets.Substitutions;
 import org.apache.shindig.gadgets.Substitutions.Type;
 
-import junit.framework.TestCase;
-
 import java.util.Arrays;
 
 public class ViewTest extends TestCase {
@@ -122,7 +122,7 @@
     substituter.addSubstitution(Type.MODULE, "ID", "3");
 
     View view = new View("test",
-        Arrays.asList(XmlUtil.parse(xml))).substitute(substituter);
+        Arrays.asList(XmlUtil.parse(xml))).substitute(substituter, false);
     assertEquals("Hello, foo Earthright 3", view.getContent());
   }
 
@@ -138,7 +138,7 @@
     substituter.addSubstitution(Type.MODULE, "ID", "123");
 
     View view = new View("test",
-        Arrays.asList(XmlUtil.parse(xml))).substitute(substituter);
+        Arrays.asList(XmlUtil.parse(xml))).substitute(substituter, false);
     assertEquals("http://up.example.org/123?dir=rtl",
                  view.getHref().toString());
   }

Modified: incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.full.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.full.xml?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.full.xml (original)
+++ incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.full.xml Mon May 19 16:04:59 2008
@@ -49,6 +49,13 @@
     </servlet-class>
   </servlet>
 
+  <servlet>
+    <servlet-name>concat</servlet-name>
+    <servlet-class>
+      org.apache.shindig.gadgets.servlet.ConcatProxyServlet
+    </servlet-class>
+  </servlet>
+
   <!-- Metadata RPC -->
   <servlet>
     <servlet-name>metadata</servlet-name>
@@ -95,6 +102,11 @@
   </servlet-mapping>
 
   <servlet-mapping>
+    <servlet-name>concat</servlet-name>
+    <url-pattern>/gadgets/concat</url-pattern>
+  </servlet-mapping>
+
+  <servlet-mapping>
     <servlet-name>xml-to-html</servlet-name>
     <url-pattern>/gadgets/ifr</url-pattern>
   </servlet-mapping>

Modified: incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.gadgets.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.gadgets.xml?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.gadgets.xml (original)
+++ incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.gadgets.xml Mon May 19 16:04:59 2008
@@ -49,6 +49,13 @@
     </servlet-class>
   </servlet>
 
+  <servlet>
+    <servlet-name>concat</servlet-name>
+    <servlet-class>
+      org.apache.shindig.gadgets.servlet.ConcatProxyServlet
+    </servlet-class>
+  </servlet>
+
   <!-- Metadata RPC -->
   <servlet>
     <servlet-name>metadata</servlet-name>
@@ -74,6 +81,11 @@
   </servlet-mapping>
 
   <servlet-mapping>
+    <servlet-name>concat</servlet-name>
+    <url-pattern>/gadgets/concat</url-pattern>
+  </servlet-mapping>
+
+  <servlet-mapping>
     <servlet-name>xml-to-html</servlet-name>
     <url-pattern>/gadgets/ifr</url-pattern>
   </servlet-mapping>

Modified: incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml?rev=658031&r1=658030&r2=658031&view=diff
==============================================================================
--- incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml (original)
+++ incubator/shindig/trunk/java/server/src/main/webapp/WEB-INF/web.xml Mon May 19 16:04:59 2008
@@ -49,6 +49,14 @@
     </servlet-class>
   </servlet>
 
+  <!-- Concat -->
+  <servlet>
+    <servlet-name>concat</servlet-name>
+    <servlet-class>
+      org.apache.shindig.gadgets.servlet.ConcatProxyServlet
+    </servlet-class>
+  </servlet>
+
   <!-- Metadata RPC -->
   <servlet>
     <servlet-name>metadata</servlet-name>
@@ -94,6 +102,11 @@
   </servlet-mapping>
 
   <servlet-mapping>
+    <servlet-name>concat</servlet-name>
+    <url-pattern>/gadgets/concat</url-pattern>
+  </servlet-mapping>
+
+  <servlet-mapping>
     <servlet-name>xml-to-html</servlet-name>
     <url-pattern>/gadgets/ifr</url-pattern>
   </servlet-mapping>