You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by et...@apache.org on 2008/03/11 10:53:01 UTC

svn commit: r635862 [2/5] - in /incubator/shindig/trunk: config/ features/core/ features/setprefs/ java/gadgets/ java/gadgets/src/main/java/org/apache/shindig/gadgets/ java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ java/gadgets/src/main/ja...

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsFeatureLoader.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsFeatureLoader.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsFeatureLoader.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsFeatureLoader.java Tue Mar 11 02:52:52 2008
@@ -18,16 +18,14 @@
 package org.apache.shindig.gadgets;
 
 import org.apache.shindig.util.ResourceLoader;
+import org.apache.shindig.util.XmlException;
 import org.apache.shindig.util.XmlUtil;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
+
+import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
 
 import java.io.File;
 import java.io.IOException;
-import java.io.StringReader;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -36,9 +34,6 @@
 import java.util.Set;
 import java.util.logging.Logger;
 
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
 /**
  * Provides a mechanism for loading a group of js features from a directory.
  *
@@ -47,12 +42,14 @@
  *
  * Usage:
  * GadgetFeatureRegistry registry = // get your feature registry.
- * JsFeatureLoader loader = new JsFeatureLoader();
+ * JsFeatureLoader loader = new JsFeatureLoader(fetcher);
  * loader.loadFeatures("res://features/", registry);
  * loader.loadFeatures("/home/user/my-features/", registry);
  */
 public class JsFeatureLoader {
 
+  private final RemoteContentFetcher fetcher;
+
   private static final Logger logger
       = Logger.getLogger("org.apache.shindig.gadgets");
 
@@ -200,17 +197,10 @@
    */
   private ParsedFeature parse(String xml, String path, boolean isResource)
       throws GadgetException {
-
-    Document doc;
+    Element doc;
     try {
-      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-      InputSource is = new InputSource(new StringReader(xml));
-      doc = factory.newDocumentBuilder().parse(is);
-    } catch (SAXException e) {
-      throw new GadgetException(GadgetException.Code.MALFORMED_XML_DOCUMENT, e);
-    } catch (ParserConfigurationException e) {
-      throw new GadgetException(GadgetException.Code.MALFORMED_XML_DOCUMENT, e);
-    } catch (IOException e) {
+      doc = XmlUtil.parse(xml);
+    } catch (XmlException e) {
       throw new GadgetException(GadgetException.Code.MALFORMED_XML_DOCUMENT, e);
     }
 
@@ -228,12 +218,13 @@
 
     NodeList gadgets = doc.getElementsByTagName("gadget");
     for (int i = 0, j = gadgets.getLength(); i < j; ++i) {
-      processContext(feature, gadgets.item(i), RenderingContext.GADGET);
+      processContext(feature, (Element)gadgets.item(i), RenderingContext.GADGET);
     }
 
     NodeList containers = doc.getElementsByTagName("container");
     for (int i = 0, j = containers.getLength(); i < j; ++i) {
-      processContext(feature, containers.item(i), RenderingContext.CONTAINER);
+      processContext(feature, (Element)containers.item(i),
+          RenderingContext.CONTAINER);
     }
 
     NodeList dependencies = doc.getElementsByTagName("dependency");
@@ -251,47 +242,53 @@
    * @param context
    * @param renderingContext
    */
-  private void processContext(ParsedFeature feature, Node context,
+  private void processContext(ParsedFeature feature, Element context,
                               RenderingContext renderingContext) {
-    NodeList libraries = context.getChildNodes();
     String syndicator = XmlUtil.getAttribute(context, "synd",
         SyndicatorConfig.DEFAULT_SYNDICATOR);
+    NodeList libraries = context.getElementsByTagName("script");
     for (int i = 0, j = libraries.getLength(); i < j; ++i) {
-      Node node = libraries.item(i);
-      String nodeValue = node.getNodeName();
-      if ("script".equals(nodeValue)) {
-        String source = XmlUtil.getAttribute(node, "src");
-        String content;
-        JsLibrary.Type type;
-        if (source == null) {
-          type = JsLibrary.Type.INLINE;
-          content = node.getTextContent();
+      Element script = (Element)libraries.item(i);
+      boolean inlineOk = XmlUtil.getBoolAttribute(script, "inline", true);
+      String source = XmlUtil.getAttribute(script, "src");
+      String content;
+      JsLibrary.Type type;
+      if (source == null) {
+        type = JsLibrary.Type.INLINE;
+        content = script.getTextContent();
+      } else {
+        content = source;
+        if (content.startsWith("http://")) {
+          type = JsLibrary.Type.URL;
+        } else if (content.startsWith("//")) {
+          type = JsLibrary.Type.URL;
+          content = content.substring(1);
+        } else if (content.startsWith("res://")) {
+          content = content.substring(6);
+          type = JsLibrary.Type.RESOURCE;
+        } else if (feature.isResource) {
+          // Note: Any features loaded as resources will assume that their
+          // paths point to resources as well.
+          content = feature.basePath + content;
+          type = JsLibrary.Type.RESOURCE;
         } else {
-          content = source;
-          if (content.startsWith("http://")) {
-            type = JsLibrary.Type.URL;
-          } else if (content.startsWith("//")) {
-            type = JsLibrary.Type.URL;
-            content = content.substring(1);
-          } else if (content.startsWith("res://")) {
-            content = content.substring(6);
-            type = JsLibrary.Type.RESOURCE;
-          } else if (feature.isResource) {
-            // Note: Any features loaded as resources will assume that their
-            // paths point to resources as well.
-            content = feature.basePath + content;
-            type = JsLibrary.Type.RESOURCE;
-          } else {
-            content = feature.basePath + content;
-            type = JsLibrary.Type.FILE;
-          }
-        }
-        JsLibrary library = JsLibrary.create(type, content);
-        for (String synd : syndicator.split(",")) {
-          feature.addLibrary(renderingContext, synd.trim(), library);
+          content = feature.basePath + content;
+          type = JsLibrary.Type.FILE;
         }
       }
+      JsLibrary library = JsLibrary.create(
+          type, content, feature.name, inlineOk ? fetcher : null);
+      for (String synd : syndicator.split(",")) {
+        feature.addLibrary(renderingContext, synd.trim(), library);
+      }
     }
+  }
+
+  /**
+   * @param fetcher
+   */
+  public JsFeatureLoader(RemoteContentFetcher fetcher) {
+    this.fetcher = fetcher;
   }
 }
 

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java Tue Mar 11 02:52:52 2008
@@ -21,6 +21,9 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
@@ -34,16 +37,34 @@
   public Type getType() {
     return type;
   }
+
+  /**
+   * The content of the library. May be optimized through minification or
+   * other compression techniques. Use debugContent to get the unmodified
+   * version.
+   */
   private final String content;
   public String getContent() {
     return content;
   }
 
+  /**
+   * Unmodified content. May be identical to content if no optimized version of
+   * the script exists.
+   */
   private final String debugContent;
   public String getDebugContent() {
     return debugContent;
   }
 
+  /**
+   * The feature that this library belongs to; may be null;
+   */
+  private final String feature;
+  public String getFeature() {
+    return feature;
+  }
+
   private static final Logger logger
       = Logger.getLogger("org.apache.shindig.gadgets");
 
@@ -86,25 +107,43 @@
    * @param content If FILE or RESOURCE, we will also look for a file
    *     named file.opt.ext for every file.ext, and if present we will
    *     use that as the standard content and file.ext as the debug content.
+   * @param feature The name of the feature that this library was created for
+   *     may be null.
+   * @param fetcher Used to retrieve Type.URL; if null, Type.URL will not be
+   *     kept as a url reference, otherwise the file will be fetched and treated
+   *     as a FILE type.
    * @return The newly created library.
    */
-  public static JsLibrary create(Type type, String content) {
+  public static JsLibrary create(Type type, String content, String feature,
+      RemoteContentFetcher fetcher) {
     String optimizedContent = null;
     String debugContent;
-    if (type == Type.FILE || type == Type.RESOURCE) {
-      if (content.endsWith(".js")) {
-        optimizedContent = loadData(
-            content.substring(0, content.length() - 3) + ".opt.js", type);
-      }
-      debugContent = loadData(content, type);
-      if (optimizedContent == null || optimizedContent.length() == 0) {
-        optimizedContent = debugContent;
-      }
-    } else {
-      debugContent = content;
-      optimizedContent = content;
+    switch (type) {
+      case FILE:
+      case RESOURCE:
+        if (content.endsWith(".js")) {
+          optimizedContent = loadData(
+              content.substring(0, content.length() - 3) + ".opt.js", type);
+        }
+        debugContent = loadData(content, type);
+        if (optimizedContent == null || optimizedContent.length() == 0) {
+          optimizedContent = debugContent;
+        }
+        break;
+      case URL:
+        if (fetcher == null) {
+          debugContent = optimizedContent = content;
+        } else {
+          type = Type.FILE;
+          debugContent = optimizedContent = loadDataFromUrl(content, fetcher);
+        }
+        break;
+      default:
+        debugContent = content;
+        optimizedContent = content;
+        break;
     }
-    return new JsLibrary(type, optimizedContent, debugContent);
+    return new JsLibrary(feature, type, optimizedContent, debugContent);
   }
 
   /**
@@ -124,6 +163,32 @@
   }
 
   /**
+   * Retrieves js content from the given url.
+   *
+   * @param url
+   * @param fetcher
+   * @return The contents of the JS file, or null if it can't be fetched.
+   */
+  private static String loadDataFromUrl(String url,
+      RemoteContentFetcher fetcher) {
+    try {
+      logger.info("Attempting to load js from: " + url);
+      URI uri = new URI(url);
+      RemoteContentRequest request = new RemoteContentRequest(uri);
+      RemoteContent response = fetcher.fetch(request);
+      if (response.getHttpStatusCode() == RemoteContent.SC_OK) {
+        return response.getResponseAsString();
+      } else {
+        logger.warning("Unable to retrieve remote library from " + url);
+        return null;
+      }
+    } catch (URISyntaxException e) {
+      logger.log(Level.WARNING, "Malformed URL: " + url, e);
+      return null;
+    }
+  }
+
+  /**
    * Loads a file
    * @param fileName
    * @return The contents of the file.
@@ -137,16 +202,16 @@
 
     File file = new File(fileName);
     if (!file.exists()) {
-      throw new RuntimeException(
-          String.format("JsLibrary file missing: %s", fileName));
+      logger.warning("File not found: " + fileName);
+      return null;
     }
     if (!file.isFile()) {
-      throw new RuntimeException(
-          String.format("JsLibrary is not a file: %s", fileName));
+      logger.warning("JsLibrary is not a file: " + fileName);
+      return null;
     }
     if (!file.canRead()) {
-      throw new RuntimeException(
-          String.format("JsLibrary cannot be read: %s", fileName));
+      logger.warning("JsLibrary cannot be read: " + fileName);
+      return null;
     }
 
     try {
@@ -171,13 +236,15 @@
      }
   }
 
-
-
   /**
+   * @param feature
    * @param type
    * @param content
+   * @param debugContent
    */
-  private JsLibrary(Type type, String content, String debugContent) {
+  private JsLibrary(String feature, Type type, String content,
+      String debugContent) {
+    this.feature = feature;
     this.type = type;
     this.content = content;
     this.debugContent = debugContent;

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibraryFeatureFactory.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibraryFeatureFactory.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibraryFeatureFactory.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibraryFeatureFactory.java Tue Mar 11 02:52:52 2008
@@ -67,13 +67,15 @@
 
   /**
    * {@inheritDoc}
+   *
+   * If context is null, all libraries will be returned rather than just
+   * the libraries for the specified context.
    */
   @Override
-  public List<JsLibrary> getJsLibraries(RenderingContext context,
-                                        ProcessingOptions options) {
+  public List<JsLibrary> getJsLibraries(GadgetContext context) {
     List<JsLibrary> libs = null;
 
-    if (context == null || options == null) {
+    if (context == null) {
       // for this special case we return all JS libraries in a single list.
       libs = new LinkedList<JsLibrary>();
       for (Map.Entry<RenderingContext, Map<String, List<JsLibrary>>> i :
@@ -83,9 +85,10 @@
         }
       }
     } else {
-      Map<String, List<JsLibrary>> contextLibs = libraries.get(context);
+      Map<String, List<JsLibrary>> contextLibs
+          = libraries.get(context.getRenderingContext());
       if (contextLibs != null) {
-        libs = contextLibs.get(options.getSyndicator());
+        libs = contextLibs.get(context.getSyndicator());
         if (libs == null) {
           // Try default.
           libs = contextLibs.get(SyndicatorConfig.DEFAULT_SYNDICATOR);
@@ -99,16 +102,8 @@
     return libs;
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
-  public void process(Gadget gadget, GadgetContext context,
-      Map<String, String> params) throws GadgetException {
-    super.process(gadget, context, params);
-    for (JsLibrary library : getJsLibraries(context.getRenderingContext(),
-                                            context.getOptions())) {
-      gadget.addJsLibrary(library);
-    }
+  public boolean isJsOnly() {
+    return true;
   }
 }

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFetcher.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFetcher.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFetcher.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleFetcher.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import org.apache.shindig.gadgets.spec.MessageBundle;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * Message bundle retrieval implementation that retrieves by the following:
+ *
+ * - Consulting the in-memory cache (just a Map)
+ * - If not found, making a RemoteContentRequest for the bundle
+ * - Parsing bundle
+ * - Storing to the cache
+ */
+public class MessageBundleFetcher implements DataFetcher<MessageBundle> {
+  private final RemoteContentFetcher fetcher;
+  private final Map<URI, MessageBundle> cache;
+
+  private final static Logger logger
+      = Logger.getLogger("org.apache.shindig.gadgets");
+
+  /** {@inheritDoc} */
+  public void invalidate(URI url) {
+    synchronized (cache) {
+      cache.remove(url);
+    }
+  }
+
+  /** {@inheritDoc} */
+  public MessageBundle fetch(URI url, boolean forceReload)
+      throws GadgetException {
+    MessageBundle bundle = null;
+    if (!forceReload) {
+      cache.get(url);
+    }
+    if (bundle == null) {
+      RemoteContentRequest request = new RemoteContentRequest(url);
+      RemoteContent response = fetcher.fetch(request);
+
+      if (response.getHttpStatusCode() == RemoteContent.SC_OK) {
+        bundle = new MessageBundle(response.getResponseAsString());
+        cache.put(url, bundle);
+      } else {
+        String error = "Unable to get content from " + url.toString();
+        logger.info(error);
+        throw new GadgetException(
+            GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT, error);
+      }
+    }
+    return bundle;
+  }
+
+  /**
+   * @param fetcher The fetcher to use for remote resource retrieval.
+   */
+  public MessageBundleFetcher(RemoteContentFetcher fetcher) {
+    this.fetcher = fetcher;
+    cache = new HashMap<URI, MessageBundle>();
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContent.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContent.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContent.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContent.java Tue Mar 11 02:52:52 2008
@@ -32,13 +32,16 @@
 public class RemoteContent {
   // Replicate HTTP status codes here.
   public final static int SC_OK = 200;
+  public final static int SC_NOT_FOUND = 404;
   public final static int SC_INTERNAL_SERVER_ERROR = 500;
 
   private final int httpStatusCode;
   private static final String DEFAULT_ENCODING = "UTF-8";
   private final String encoding;
 
-  public static final RemoteContent ERROR = new RemoteContent();
+  public static final RemoteContent ERROR
+      = new RemoteContent(SC_INTERNAL_SERVER_ERROR);
+  public static final RemoteContent NOT_FOUND = new RemoteContent(SC_NOT_FOUND);
 
   // Used to lazily convert to a string representation of the input.
   private String responseString = null;
@@ -48,8 +51,8 @@
   /**
    * Create a dummy empty map. Access via RemoteContent.ERROR
    */
-  private RemoteContent() {
-    this.httpStatusCode = SC_INTERNAL_SERVER_ERROR;
+  private RemoteContent(int statusCode) {
+    this.httpStatusCode = statusCode;
     this.responseBytes = new byte[0];
     this.encoding = DEFAULT_ENCODING;
     this.headers = Collections.emptyMap();

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentFetcher.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentFetcher.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentFetcher.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentFetcher.java Tue Mar 11 02:52:52 2008
@@ -19,22 +19,10 @@
 
 
 public interface RemoteContentFetcher {
-
   /**
    * Fetch content using the HTTP GET method
    * @param request The request to fetch.
-   * @param options Additional options
    * @return RemoteContent
    */
-  public RemoteContent fetch(RemoteContentRequest request,
-                             ProcessingOptions options);
-
-  /**
-   * Fetch content using the HTTP POST method
-   * @param request The request to fetch.
-   * @param options Additional options
-   * @return RemoteContent
-   */
-  public RemoteContent fetchByPost(RemoteContentRequest request,
-                                   ProcessingOptions options);
+  public RemoteContent fetch(RemoteContentRequest request);
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java Tue Mar 11 02:52:52 2008
@@ -105,21 +105,37 @@
     }
   }
 
+  private final String method;
+  public String getMethod() {
+    return method;
+  }
+
   private final URI uri;
   public URI getUri() {
     return uri;
   }
 
+  private final Options options;
+  public Options getOptions() {
+    return options;
+  }
+
   /**
    *
+   * @param method
    * @param uri
    * @param headers
    * @param postBody
+   * @param options
    */
-  public RemoteContentRequest(URI uri,
+  public RemoteContentRequest(String method,
+                              URI uri,
                               Map<String, List<String>> headers,
-                              byte[] postBody) {
+                              byte[] postBody,
+                              Options options) {
+    this.method = method;
     this.uri = uri;
+    this.options = options;
     // Copy the headers
     if (headers == null) {
       this.headers = Collections.emptyMap();
@@ -148,16 +164,111 @@
     }
   }
 
+  /**
+   * Basic GET request.
+   *
+   * @param uri
+   */
+  public RemoteContentRequest(URI uri) {
+    this("GET", uri, null, null, DEFAULT_OPTIONS);
+  }
+
+  /**
+   * GET with options
+   *
+   * @param uri
+   * @param options
+   */
+  public RemoteContentRequest(URI uri, Options options) {
+    this("GET", uri, null, null, options);
+  }
+
+  /**
+   * GET request with custom headers and default options
+   * @param uri
+   * @param headers
+   */
   public RemoteContentRequest(URI uri, Map<String, List<String>> headers) {
-    this(uri, headers, null);
+    this("GET", uri, headers, null, DEFAULT_OPTIONS);
   }
 
+  /**
+   * GET request with custom headers + options
+   * @param uri
+   * @param headers
+   * @param options
+   */
+  public RemoteContentRequest(URI uri, Map<String, List<String>> headers,
+      Options options) {
+    this("GET", uri, headers, null, options);
+  }
+
+  /**
+   * Basic POST request
+   * @param uri
+   * @param postBody
+   */
   public RemoteContentRequest(URI uri, byte[] postBody) {
-    this(uri, null, postBody);
+    this("POST", uri, null, postBody, DEFAULT_OPTIONS);
   }
 
-  public RemoteContentRequest(URI uri) {
-    this(uri, null, null);
+  /**
+   * POST request with options
+   * @param uri
+   * @param postBody
+   * @param options
+   */
+  public RemoteContentRequest(URI uri, byte[] postBody, Options options) {
+    this("POST", uri, null, postBody, options);
+  }
+
+  /**
+   * POST request with headers
+   * @param uri
+   * @param headers
+   * @param postBody
+   */
+  public RemoteContentRequest(URI uri, Map<String, List<String>> headers,
+      byte[] postBody) {
+    this("POST", uri, headers, postBody, DEFAULT_OPTIONS);
+  }
+
+  /**
+   * POST request with options + headers
+   * @param uri
+   * @param headers
+   * @param postBody
+   * @param options
+   */
+  public RemoteContentRequest(URI uri, Map<String, List<String>> headers,
+      byte[] postBody, Options options) {
+    this("POST", uri, headers, postBody, options);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder buf = new StringBuilder();
+    buf.append(method).append(" ").append(uri.getPath()).append(" HTTP/1.1\r\n")
+       .append("Host: ").append(uri.getHost())
+       .append(uri.getPort() == 80 ? "" : ":" + uri.getPort())
+       .append("\r\n");
+
+    for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+      buf.append(entry.getKey()).append(": ");
+      boolean first = false;
+      for (String header : entry.getValue()) {
+        if (!first) {
+          first = true;
+        } else {
+          buf.append(", ");
+        }
+        buf.append(header);
+      }
+      buf.append("\r\n");
+    }
+   buf.append("\r\n");
+   buf.append(new String(postBody));
+   return buf.toString();
   }
 
   @Override
@@ -165,10 +276,23 @@
     if (rhs == this) {return true;}
     if (rhs instanceof RemoteContentRequest) {
       RemoteContentRequest req = (RemoteContentRequest)rhs;
-      return uri.equals(req.uri) &&
+      return method.equals(req.method) &&
+             uri.equals(req.uri) &&
              Arrays.equals(postBody, req.postBody) &&
              headers.equals(req.headers);
     }
     return false;
+  }
+
+  public static final Options DEFAULT_OPTIONS = new Options();
+
+  /**
+   * Bag of options for making a request.
+   *
+   * This object is mutable to keep us sane. Don't mess with it once you've
+   * sent it to RemoteContentRequest or bad things might happen.
+   */
+  public static class Options {
+    public boolean ignoreCache = false;
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Substitutions.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Substitutions.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Substitutions.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Substitutions.java Tue Mar 11 02:52:52 2008
@@ -17,15 +17,15 @@
  */
 package org.apache.shindig.gadgets;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.EnumMap;
 
 /**
  * Performs string substitutions for message bundles, user prefs, and bidi
  * variables.
- *
- * @author etnu
  */
 public class Substitutions {
 
@@ -106,20 +106,12 @@
   }
 
   /**
-   * Substitutes all substitutions into the given string. The order of
-   * substitutions is the same as defined for Type.
-   *
-   * @param input
-   *        The base string, with substitution markers.
-   * @return The substituted string or null if {@code input} is null.
+   * @param type
+   * @param name
+   * @return The substitution set under the given type / name, or null.
    */
-  public String substitute(String input) {
-    if (input != null) {
-      for (Type type : Type.values()) {
-        input = substituteType(type, input);
-      }
-    }
-    return input;
+  public String getSubstitution(Type type, String name) {
+    return substitutions.get(type).get(name);
   }
 
   /**
@@ -133,9 +125,19 @@
    *        The base string, with substitution markers.
    * @return The substituted string.
    */
-  public String substituteType(Type type, String input) {
-    if (input == null || substitutions.get(type).size() == 0 ||
-        !input.contains(type.prefix)) {
+  public String substituteString(Type type, String input) {
+    if (input == null) {
+      return null;
+    }
+
+    if (type == null) {
+      for (Type t : Type.values()) {
+        input = substituteString(t, input);
+      }
+      return input;
+    }
+
+    if (substitutions.get(type).size() == 0 || !input.contains(type.prefix)) {
       return input;
     }
 
@@ -167,5 +169,22 @@
     }
 
     return output.toString();
+  }
+
+  /**
+   * Substitutes a uri
+   * @param type The type to substitute, or null for all types.
+   * @param uri
+   * @return The substituted uri, or a dummy value if the result is invalid.
+   */
+  public URI substituteUri(Type type, URI uri) {
+    if (uri == null) {
+      return null;
+    }
+    try {
+      return new URI(substituteString(type, uri.toString()));
+    } catch (URISyntaxException e) {
+      return URI.create("");
+    }
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SyndicatorConfig.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SyndicatorConfig.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SyndicatorConfig.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SyndicatorConfig.java Tue Mar 11 02:52:52 2008
@@ -20,6 +20,7 @@
 package org.apache.shindig.gadgets;
 
 import org.apache.shindig.util.ResourceLoader;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -88,6 +89,9 @@
     JSONObject syndicatorData = config.get(syndicator);
     if (syndicatorData == null) {
       return null;
+    }
+    if (parameter == null) {
+      return syndicatorData;
     }
     return syndicatorData.opt(parameter);
   }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefSubstituter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefSubstituter.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefSubstituter.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefSubstituter.java Tue Mar 11 02:52:52 2008
@@ -17,69 +17,28 @@
  */
 package org.apache.shindig.gadgets;
 
-import org.json.JSONException;
-import org.json.JSONObject;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.UserPref;
 
-import java.util.Map;
+import org.apache.commons.lang.StringEscapeUtils;
 
 /**
  * Substitutes user prefs into the spec.
  */
-public class UserPrefSubstituter implements GadgetFeatureFactory {
-  private final static GadgetFeature feature
-      = new UserPrefSubstituterFeature();
-
-  /**
-  * {@inheritDoc}
-  */
-  public GadgetFeature create() {
-    return feature;
-  }
-}
-
-class UserPrefSubstituterFeature extends GadgetFeature {
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void process(Gadget gadget, GadgetContext context,
-                      Map<String, String> params) throws GadgetException {
-    super.process(gadget, context, params);
-    Substitutions substitutions = gadget.getSubstitutions();
-    UserPrefs upValues = gadget.getUserPrefValues();
-
-    JSONObject json = null;
-
-    if (context.getRenderingContext() == RenderingContext.GADGET) {
-      json = new JSONObject();
-    }
-
-    for (GadgetSpec.UserPref pref : gadget.getUserPrefs()) {
+public class UserPrefSubstituter {
+  public static void addSubstitutions(Substitutions substituter,
+      GadgetSpec spec, UserPrefs values) {
+    for (UserPref pref : spec.getUserPrefs()) {
       String name = pref.getName();
-      String value = upValues.getPref(name);
+      String value = values.getPref(name);
       if (value == null) {
         value = pref.getDefaultValue();
-      }
-      if (value == null) {
-        value = "";
-      }
-      substitutions.addSubstitution(Substitutions.Type.USER_PREF, name, value);
-
-      if (json != null) {
-        try {
-          json.put(name, value);
-        } catch (JSONException e) {
-          throw new RuntimeException(e);
+        if (value == null) {
+          value = "";
         }
       }
-    }
-
-    if (json != null) {
-      String setPrefFmt = "gadgets.prefs_.setPref(%d, %s);";
-      int moduleId = gadget.getId().getModuleId();
-      String setPrefStr = String.format(setPrefFmt, moduleId, json.toString());
-      gadget.addJsLibrary(JsLibrary.create(JsLibrary.Type.INLINE, setPrefStr));
+      substituter.addSubstitution(Substitutions.Type.USER_PREF, name,
+          StringEscapeUtils.escapeHtml(value));
     }
   }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefs.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefs.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefs.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefs.java Tue Mar 11 02:52:52 2008
@@ -43,12 +43,17 @@
     return prefs.get(name);
   }
 
+  @Override
+  public String toString() {
+    return prefs.toString();
+  }
+
   /**
-   * @param prefs
+   * @param prefs The preferences to populate.
    */
   public UserPrefs(Map<String, String> prefs) {
-    Map<String, String> tempMap = new HashMap<String, String>(prefs);
-    this.prefs = Collections.unmodifiableMap(tempMap);
+    this.prefs
+        = Collections.unmodifiableMap(new HashMap<String, String>(prefs));
   }
 
   /**

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/CrossServletState.java Tue Mar 11 02:52:52 2008
@@ -20,16 +20,16 @@
 package org.apache.shindig.gadgets.http;
 
 import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetContext;
 import org.apache.shindig.gadgets.GadgetServer;
 import org.apache.shindig.gadgets.GadgetSigner;
-import org.apache.shindig.gadgets.ProcessingOptions;
 
+import java.util.Set;
 import java.util.logging.Logger;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
 
 /**
  * Loads shared configuration and creates appropriate class instances for
@@ -79,10 +79,9 @@
   public abstract GadgetServer getGadgetServer();
 
   /**
-   * @param req The request that a signing token is needed for.
-   * @return A unique GadgetSigner for the request
+   * @return A gadget signer implementation
    */
-  public abstract GadgetSigner getGadgetSigner(HttpServletRequest req);
+  public abstract GadgetSigner getGadgetSigner();
 
   /**
    * Constructs a url for retrieving javascript for the given
@@ -91,17 +90,17 @@
    * @param features
    * @return The url to retrieve the appropriate JS.
    */
-  public abstract String getJsUrl(String[] features, ProcessingOptions opts);
+  public abstract String getJsUrl(Set<String> features, GadgetContext context);
 
   /**
    * Constructs a url for generating an iframe for the given gadget.
    * This only applies for RPC calls that must generate an iframe.
    *
-   * TODO: The second parameter here should be something else (perhaps a
-   * context object). A better choice would probably be to add the view params
-   * to ProcessingOptions and pass that here.
+   * @param gadget
+   * @return The url for the iframe; may have both query string and fragment
+   *     parameters, so caution should be taken when adding your own data.
    */
-  public abstract String getIframeUrl(Gadget gadget, ProcessingOptions opts);
+  public abstract String getIframeUrl(Gadget gadget);
 
   /**
    * Initializes this handler using the provided implementation.

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/DefaultCrossServletState.java Tue Mar 11 02:52:52 2008
@@ -19,10 +19,11 @@
 
 package org.apache.shindig.gadgets.http;
 
-import org.apache.shindig.gadgets.BasicGadgetDataCache;
+import org.apache.shindig.gadgets.BasicGadgetBlacklist;
 import org.apache.shindig.gadgets.BasicGadgetSigner;
 import org.apache.shindig.gadgets.BasicRemoteContentFetcher;
 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.GadgetFeatureFactory;
@@ -30,23 +31,26 @@
 import org.apache.shindig.gadgets.GadgetServer;
 import org.apache.shindig.gadgets.GadgetServerConfig;
 import org.apache.shindig.gadgets.GadgetSigner;
-import org.apache.shindig.gadgets.GadgetSpec;
+import org.apache.shindig.gadgets.GadgetSpecFetcher;
 import org.apache.shindig.gadgets.JsLibrary;
-import org.apache.shindig.gadgets.MessageBundle;
-import org.apache.shindig.gadgets.ProcessingOptions;
+import org.apache.shindig.gadgets.MessageBundleFetcher;
+import org.apache.shindig.gadgets.RemoteContentFetcher;
 import org.apache.shindig.gadgets.SyndicatorConfig;
 import org.apache.shindig.gadgets.UserPrefs;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.apache.shindig.gadgets.spec.View;
+import org.apache.shindig.util.HashUtil;
 
+import java.io.File;
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executors;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
 
 /**
  * A handler which uses all of the basic versions of classes.
@@ -75,7 +79,7 @@
    * Just returns the same gadget signer no matter the request.
    */
   @Override
-  public GadgetSigner getGadgetSigner(HttpServletRequest req) {
+  public GadgetSigner getGadgetSigner() {
     return gadgetSigner;
   }
 
@@ -83,14 +87,14 @@
    * {@inheritDoc}
    */
   @Override
-  public String getIframeUrl(Gadget gadget, ProcessingOptions opts) {
-    // We don't have any meaningful data in the current request anyway, so
-    // we'll just do this statically.
+  public String getIframeUrl(Gadget gadget) {
+    GadgetContext context = gadget.getContext();
     StringBuilder buf = new StringBuilder();
     try {
-      String url = gadget.getId().getURI().toString();
-      GadgetSpec.View view = gadget.getView(opts.getView());
-      if (view.getType().equals(GadgetSpec.ContentType.HTML)) {
+      GadgetSpec spec = gadget.getSpec();
+      String url = context.getUrl().toString();
+      View view = spec.getView(context.getView());
+      if (view.getType().equals(View.ContentType.HTML)) {
         buf.append(iframePath)
            .append("url=")
            .append(URLEncoder.encode(url, "UTF-8"))
@@ -105,10 +109,11 @@
         }
       }
 
-      buf.append("mid=").append(gadget.getId().getModuleId());
-      buf.append("&synd=").append(opts.getSyndicator());
+      buf.append("mid=").append(context.getModuleId());
+      buf.append("&synd=").append(context.getSyndicator());
+      buf.append("&v=").append(gadget.getSpec().getChecksum());
 
-      UserPrefs prefs = gadget.getUserPrefValues();
+      UserPrefs prefs = context.getUserPrefs();
       for (Map.Entry<String, String> entry : prefs.getPrefs().entrySet()) {
         buf.append("&up_")
            .append(entry.getKey())
@@ -119,8 +124,6 @@
       throw new RuntimeException("UTF-8 Not supported!", e);
     }
 
-    // TODO: extract user prefs, current view, etc. from <req>. Currently
-    // consumers of the response are on their own for this.
     return buf.toString();
   }
 
@@ -128,10 +131,10 @@
    * {@inheritDoc}
    */
   @Override
-  public String getJsUrl(String[] features, ProcessingOptions options) {
+  public String getJsUrl(Set<String> features, GadgetContext context) {
     StringBuilder buf = new StringBuilder();
     buf.append(jsPath);
-    if (features == null || features.length == 0) {
+    if (features == null || features.size() == 0) {
       buf.append("core");
     } else {
       boolean firstDone = false;
@@ -147,9 +150,9 @@
     buf.append(".js?v=")
        .append(jsCacheParam)
        .append("&synd=")
-       .append(options.getSyndicator())
+       .append(context.getSyndicator())
        .append("&debug=")
-       .append(options.getDebug() ? "1" : "0");
+       .append(context.getDebug() ? "1" : "0");
     return buf.toString();
   }
 
@@ -168,21 +171,33 @@
       iframePath = DEFAULT_IFRAME_PREFIX;
     }
 
-    // features could be null, but that would probably be a bad idea.
-    String features = context.getInitParameter("features");
-    String syndicators = context.getInitParameter("syndicators");
     try {
+      String features = context.getInitParameter("features");
+      String syndicators = context.getInitParameter("syndicators");
+      String blacklist = context.getInitParameter("blacklist");
       gadgetSigner = new BasicGadgetSigner();
+      RemoteContentFetcher fetcher = new BasicRemoteContentFetcher(1024 * 1024);
       SyndicatorConfig syndicatorConfig = new SyndicatorConfig(syndicators);
-      GadgetFeatureRegistry registry = new GadgetFeatureRegistry(features);
+      GadgetFeatureRegistry registry
+          = new GadgetFeatureRegistry(features, fetcher);
 
       GadgetServerConfig config =  new GadgetServerConfig()
           .setExecutor(Executors.newCachedThreadPool())
-          .setMessageBundleCache(new BasicGadgetDataCache<MessageBundle>())
-          .setSpecCache(new BasicGadgetDataCache<GadgetSpec>())
-          .setContentFetcher(new BasicRemoteContentFetcher(1024 * 1024))
+          .setMessageBundleFetcher(new MessageBundleFetcher(fetcher))
+          .setGadgetSpecFetcher(new GadgetSpecFetcher(fetcher))
+          .setContentFetcher(fetcher)
           .setFeatureRegistry(registry)
           .setSyndicatorConfig(syndicatorConfig);
+
+      if (blacklist != null) {
+        File file = new File(blacklist);
+        try {
+          config.setGadgetBlacklist(new BasicGadgetBlacklist(file));
+        } catch (IOException e) {
+          throw new GadgetException(GadgetException.Code.INVALID_CONFIG,
+              "Unable to load blacklist file: " + blacklist);
+        }
+      }
       gadgetServer = new GadgetServer(config);
 
       // Grab all static javascript, concatenate it together, and generate
@@ -193,35 +208,12 @@
           registry.getAllFeatures().entrySet()) {
         GadgetFeatureFactory factory = entry.getValue().getFeature();
         GadgetFeature feature = factory.create();
-        for (JsLibrary library : feature.getJsLibraries(null, null)) {
+        for (JsLibrary library : feature.getJsLibraries(null)) {
           jsBuf.append(library.getContent());
         }
       }
 
-      // Include the syndicator in the hash
-      for (String syndicator : syndicatorConfig.getSyndicators()) {
-        jsBuf.append(syndicatorConfig.getJsonObject(syndicator,  null)
-            .toString());
-      }
-
-      MessageDigest md;
-      try {
-        md = MessageDigest.getInstance("MD5");
-      } catch (NoSuchAlgorithmException noMD5) {
-        try {
-          md = MessageDigest.getInstance("SHA");
-        } catch (NoSuchAlgorithmException noSha) {
-          throw new ServletException("No suitable MessageDigest found!");
-        }
-      }
-      byte[] hash = md.digest(jsBuf.toString().getBytes());
-      // Convert to hex. This might be a waste of bytes (32) -- could be
-      // replaced with a base64 implementation.
-      StringBuffer hexString = new StringBuffer();
-      for (byte b : hash) {
-        hexString.append(Integer.toHexString(0xFF & b));
-      }
-      jsCacheParam = hexString.toString();
+      jsCacheParam = HashUtil.checksum(jsBuf.toString().getBytes());
     } catch (GadgetException e) {
       throw new ServletException(e);
     }

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderer.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderer.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderer.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderer.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,383 @@
+/*
+ * 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.http;
+
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetContentFilter;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.GadgetFeatureRegistry;
+import org.apache.shindig.gadgets.GadgetServerConfigReader;
+import org.apache.shindig.gadgets.JsLibrary;
+import org.apache.shindig.gadgets.SyndicatorConfig;
+import org.apache.shindig.gadgets.spec.MessageBundle;
+import org.apache.shindig.gadgets.spec.View;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handles processing a single http request
+ */
+public class GadgetRenderer {
+  private static final String CAJA_PARAM = "caja";
+  private static final String LIBS_PARAM_NAME = "libs";
+  private static final Logger logger
+      = Logger.getLogger("org.apache.shindig.gadgets");
+  public static final String STRICT_MODE_DOCTYPE = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">";
+
+  private final HttpServletRequest request;
+  private final HttpServletResponse response;
+  private final CrossServletState state;
+  private final GadgetContext context;
+  private final List<GadgetContentFilter> filters;
+
+  /**
+   * Processes a single rendering request and produces output html or errors.
+   *
+   * @throws IOException
+   */
+  public void process() throws IOException {
+    URI url = context.getUrl();
+
+    if (url == null) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+          "Missing or malformed url parameter");
+      return;
+    }
+
+    if (!"http".equals(url.getScheme()) && !"https".equals(url.getScheme())) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+                         "Unsupported scheme (must be http or https).");
+      return;
+    }
+
+    if (!validateParent()) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+          "Unsupported parent parameter. Check your syndicator code.");
+      return;
+    }
+
+    if (getUseCaja(request)) {
+      filters.add(new CajaContentFilter(url));
+    }
+
+    try {
+      Gadget gadget = state.getGadgetServer().processGadget(context);
+      outputGadget(gadget);
+    } catch (GadgetException e) {
+      outputErrors(e);
+    }
+  }
+
+  /**
+   * Renders a successfully processed gadget.
+   *
+   * @param gadget
+   * @throws IOException
+   * @throws GadgetException
+   */
+  private void outputGadget(Gadget gadget) throws IOException, GadgetException {
+    String viewName = context.getView();
+    View view = gadget.getSpec().getView(viewName);
+    if (view == null) {
+      throw new GadgetException(GadgetException.Code.UNKNOWN_VIEW_SPECIFIED,
+          "No appropriate view could be found for this gadget");
+    }
+    switch(view.getType()) {
+      case HTML:
+        outputHtmlGadget(gadget, view);
+        break;
+      case URL:
+        outputUrlGadget(gadget, view);
+        break;
+    }
+  }
+
+  /**
+   * Handles type=html gadget output.
+   *
+   * @param gadget
+   * @param view
+   * @throws IOException
+   * @throws GadgetException
+   */
+  private void outputHtmlGadget(Gadget gadget, View view)
+      throws IOException, GadgetException {
+    response.setContentType("text/html; charset=UTF-8");
+    StringBuilder markup = new StringBuilder();
+
+    if (!view.getQuirks()) {
+      markup.append(STRICT_MODE_DOCTYPE);
+    }
+
+    // TODO: Substitute gadgets.skins values in here.
+    String boilerPlate
+        = "<style type=\"text/css\">" +
+          "body,td,div,span,p{font-family:arial,sans-serif;}" +
+          "a {color:#0000cc;}a:visited {color:#551a8b;}" +
+          "a:active {color:#ff0000;}" +
+          "body{margin: 0px;padding: 0px;background-color:white;}" +
+          "</style></head></body>";
+    markup.append(boilerPlate);
+    StringBuilder externJs = new StringBuilder();
+    StringBuilder inlineJs = new StringBuilder();
+    String externFmt = "<script src=\"%s\"></script>";
+    String forcedLibs = request.getParameter("libs");
+    Set<String> libs;
+    if (forcedLibs == null) {
+      libs = new HashSet<String>();
+    } else {
+      libs = new HashSet<String>();
+      for (String lib : forcedLibs.split(":")) {
+        libs.add(lib);
+      }
+    }
+
+    // Forced libs are always done first.
+    if (libs.size() > 0) {
+      String jsUrl = state.getJsUrl(libs, context);
+      markup.append(String.format(externFmt, jsUrl));
+
+      // Transitive dependencies must be added. This will always include core
+      // so is therefore always "safe".
+      Set<GadgetFeatureRegistry.Entry> deps
+          = new HashSet<GadgetFeatureRegistry.Entry>();
+      Set<String> dummy = new HashSet<String>();
+      GadgetFeatureRegistry registry
+          = state.getGadgetServer().getConfig().getFeatureRegistry();
+      registry.getIncludedFeatures(libs, deps, dummy);
+      for (GadgetFeatureRegistry.Entry dep : deps) {
+        libs.add(dep.getName());
+      }
+    }
+
+    // Inline any libs that weren't forced
+    for (JsLibrary library : gadget.getJsLibraries()) {
+      JsLibrary.Type type = library.getType();
+      if (library.getType().equals(JsLibrary.Type.URL)) {
+        externJs.append(String.format(externFmt, library.getContent()));
+      } else {
+        if (!libs.contains(library.getFeature())) {
+          // already pulled this file in from the shared contents.
+          if (context.getDebug()) {
+            inlineJs.append(library.getDebugContent());
+          } else {
+            inlineJs.append(library.getContent());
+          }
+        }
+      }
+    }
+
+    for (JsLibrary library : gadget.getJsLibraries()) {
+      libs.add(library.getFeature());
+    }
+
+    appendJsConfig(libs, inlineJs);
+
+    // message bundles for prefs object.
+    MessageBundle bundle = gadget.getMessageBundle();
+
+    String msgs = new JSONObject(bundle.getMessages()).toString();
+    inlineJs.append("gadgets.Prefs.setMessages_(").append(msgs).append(");");
+
+    if (inlineJs.length() > 0) {
+      markup.append("<script><!--\n").append(inlineJs)
+            .append("\n-->\n</script>");
+    }
+
+    if (externJs.length() > 0) {
+      markup.append(externJs);
+    }
+
+    List<GadgetException> gadgetExceptions = new LinkedList<GadgetException>();
+
+    String content = view.getContent();
+    for (GadgetContentFilter filter : filters) {
+      content = filter.filter(content);
+    }
+
+    markup.append(content)
+          .append("<script>gadgets.util.runOnLoadHandlers();</script>")
+          .append("</body></html>");
+    if (request.getParameter("v") != null) {
+      // Versioned files get cached indefinitely
+      HttpUtil.setCachingHeaders(response, 0);
+    } else {
+      // Unversioned files get cached for 5 minutes.
+      // TODO: This should be configurable
+      HttpUtil.setCachingHeaders(response, 60 * 5);
+    }
+    response.getWriter().print(markup.toString());
+  }
+
+  /**
+   * Outputs a url type gadget by redirecting.
+   *
+   * @param gadget
+   * @param view
+   * @throws IOException
+   */
+  private void outputUrlGadget(Gadget gadget,  View view) throws IOException {
+    // TODO: generalize this as injectedArgs on Gadget object
+
+    // Preserve existing query string parameters.
+    URI href = view.getHref();
+    String queryStr = href.getQuery();
+    StringBuilder query = new StringBuilder(queryStr == null ? "" : queryStr);
+
+    // TODO: figure out a way to make this work with forced libs.
+    Set<String> libs
+        = gadget.getSpec().getModulePrefs().getFeatures().keySet();
+    appendLibsToQuery(libs, query);
+
+    try {
+      href = new URI(href.getScheme(),
+                     href.getUserInfo(),
+                     href.getHost(),
+                     href.getPort(),
+                     href.getPath(),
+                     query.toString(),
+                     href.getFragment());
+    } catch (URISyntaxException e) {
+      // Not really ever going to happen; input values are already OK.
+      response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                         e.getMessage());
+    }
+    response.sendRedirect(href.toString());
+  }
+
+  /**
+   * Displays errors.
+   * @param error
+   * @throws IOException
+   */
+  private void outputErrors(GadgetException error) throws IOException {
+    // Log the errors here for now. We might want different severity levels
+    // for different error codes.
+    logger.log(Level.INFO, "Failed to render gadget", error);
+    String message = error.getMessage();
+    if (message == null || message.length() == 0) {
+      message = "Failed to render gadget: " + error.getCode().toString();
+    }
+    response.getWriter().print(message);
+  }
+
+  /**
+   * Appends libs to the query string.
+   * @param libs
+   * @param query
+   */
+  private void appendLibsToQuery(Set<String> libs, StringBuilder query) {
+    query.append("&")
+         .append(LIBS_PARAM_NAME)
+         .append("=")
+         .append(state.getJsUrl(libs, context));
+  }
+
+  /**
+   * @param req
+   * @return Whether or not to use caja.
+   */
+  protected boolean getUseCaja(HttpServletRequest req) {
+    String cajaParam = request.getParameter(CAJA_PARAM);
+    return "1".equals(cajaParam);
+  }
+
+  /**
+   * @param reqs The features you require.
+   * @param js Existing js, to which the configuration will be appended.
+   */
+  private void appendJsConfig(Set<String> reqs, StringBuilder js) {
+    GadgetServerConfigReader config = state.getGadgetServer().getConfig();
+    SyndicatorConfig syndConf = config.getSyndicatorConfig();
+    js.append(HttpUtil.getJsConfig(syndConf, context, reqs));
+  }
+
+  /**
+   * Validates that the parent parameter was acceptable.
+   *
+   * @return True if the parent parameter is valid for the current
+   *     syndicator.
+   */
+  private boolean validateParent() {
+    String syndicator = request.getParameter("synd");
+    if (syndicator == null) {
+      syndicator = SyndicatorConfig.DEFAULT_SYNDICATOR;
+    }
+
+    String parent = request.getParameter("parent");
+
+    if (parent == null) {
+      // If there is no parent parameter, we are still safe because no
+      // dependent code ever has to trust it anyway.
+      return true;
+    }
+
+    SyndicatorConfig syndConf
+        = state.getGadgetServer().getConfig().getSyndicatorConfig();
+
+    try {
+      JSONArray parents = syndConf.getJsonArray(syndicator, "gadgets.parent");
+
+      if (parents == null) {
+        return true;
+      } else {
+        // We need to check each possible parent parameter against this regex.
+        for (int i = 0, j = parents.length(); i < j; ++i) {
+          // TODO: Should patterns be cached? Recompiling every request
+          // seems wasteful.
+          if (Pattern.matches(parents.getString(i), parent)) {
+            return true;
+          }
+        }
+      }
+    } catch (JSONException e) {
+      logger.log(Level.WARNING, "Configuration error", e);
+    }
+    return false;
+  }
+
+
+  public GadgetRenderer(HttpServletRequest request,
+                        HttpServletResponse response,
+                        CrossServletState state) {
+    this.request = request;
+    this.response = response;
+    this.state = state;
+    context = new HttpGadgetContext(request);
+    filters = new LinkedList<GadgetContentFilter>();
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java?rev=635862&r1=635861&r2=635862&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingServlet.java Tue Mar 11 02:52:52 2008
@@ -17,39 +17,7 @@
  */
 package org.apache.shindig.gadgets.http;
 
-import org.apache.shindig.gadgets.Gadget;
-import org.apache.shindig.gadgets.GadgetContentFilter;
-import org.apache.shindig.gadgets.GadgetException;
-import org.apache.shindig.gadgets.GadgetServer;
-import org.apache.shindig.gadgets.GadgetServerConfigReader;
-import org.apache.shindig.gadgets.GadgetSpec;
-import org.apache.shindig.gadgets.GadgetView;
-import org.apache.shindig.gadgets.JsLibrary;
-import org.apache.shindig.gadgets.ProcessingOptions;
-import org.apache.shindig.gadgets.RenderingContext;
-import org.apache.shindig.gadgets.SyndicatorConfig;
-import org.apache.shindig.gadgets.UserPrefs;
-import org.apache.shindig.gadgets.GadgetFeatureRegistry.Entry;
-import org.apache.shindig.gadgets.GadgetSpec.View;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Pattern;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
@@ -61,429 +29,25 @@
  * Servlet for rendering Gadgets, typically in an IFRAME.
  */
 public class GadgetRenderingServlet extends HttpServlet {
-  private CrossServletState servletState;
-  private static final String CAJA_PARAM = "caja";
-  private static final String USERPREF_PARAM_PREFIX = "up_";
-  private static final String LIBS_PARAM_NAME = "libs";
-  private static final Logger logger
-      = Logger.getLogger("org.apache.shindig.gadgets");
+  private CrossServletState state;
 
   @Override
   public void init(ServletConfig config) throws ServletException {
-    servletState = CrossServletState.get(config);
+    state = CrossServletState.get(config);
   }
 
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp)
       throws IOException {
-    String urlParam = req.getParameter("url");
-    if (urlParam == null) {
-      resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
-                     "Missing required parameter: url");
-      return;
-    }
-
-    URI uri = null;
-    try {
-      uri = new URI(urlParam);
-    } catch (URISyntaxException e) {
-      resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
-                     String.format("Malformed URL %s, reason: %s",
-                                   urlParam,
-                                   e.getMessage()));
-      return;
-    }
-
-    if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme())) {
-      resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
-                     "Unsupported scheme (must be http or https).");
-      return;
-    }
-
-    if (!validateParent(req)) {
-      logger.info("Invalid parent");
-      resp.sendError(HttpServletResponse.SC_BAD_REQUEST,
-          "Unsupported parent parameter. Check your syndicator code.");
+    // If an If-Modified-Since header is ever provided, we always say
+    // not modified. This is because when there actually is a change,
+    // cache busting should occur.
+    if (req.getHeader("If-Modified-Since") != null &&
+        req.getParameter("v") != null) {
+      resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
       return;
     }
-
-    int moduleId = 0;
-    String mid = req.getParameter("mid");
-    if (mid != null) {
-      moduleId = Integer.parseInt(mid);
-    }
-
-    ProcessingOptions options = new HttpProcessingOptions(req);
-    BasicHttpContext context = new BasicHttpContext(req);
-    GadgetView.ID gadgetId = new Gadget.GadgetId(uri, moduleId);
-
-    // Prepare a list of GadgetContentFilters applied to the output
-    List<GadgetContentFilter> contentFilters =
-        new LinkedList<GadgetContentFilter>();
-    if (getUseCaja(req)) {
-      contentFilters.add(new CajaContentFilter(uri));
-    }
-
-    Gadget gadget = null;
-    String view = req.getParameter("view");
-    view = (view == null || view.length() == 0) ? GadgetSpec.DEFAULT_VIEW : view;
-    try {
-      gadget = servletState.getGadgetServer().processGadget(gadgetId,
-          getPrefsFromRequest(req), context.getLocale(),
-          RenderingContext.GADGET, options);
-      outputGadget(gadget, options, contentFilters, resp);
-    } catch (GadgetServer.GadgetProcessException e) {
-      outputErrors(e, resp);
-    }
-  }
-
-  /**
-   * Renders a successfully processed gadget.
-   *
-   * @param gadget
-   * @param options
-   * @param contentFilters
-   * @param resp
-   * @throws IOException
-   * @throws GadgetServer.GadgetProcessException
-   */
-  private void outputGadget(Gadget gadget,
-                            ProcessingOptions options,
-                            List<GadgetContentFilter> contentFilters,
-                            HttpServletResponse resp)
-      throws IOException, GadgetServer.GadgetProcessException {
-    View view = gadget.getView(options.getView());
-    if (view == null) {
-      String viewName = options.getView();
-      if (viewName != null) {
-        throw new GadgetServer.GadgetProcessException(
-            GadgetException.Code.INVALID_PARAMETER,
-            "Requested view '" + viewName + "' does not exist in this gadget.");
-      } else {
-        throw new GadgetServer.GadgetProcessException(
-            GadgetException.Code.MISSING_PARAMETER,
-            "View must be specified as Gadget does not have default view.");
-      }
-    }
-    switch(view.getType()) {
-      case HTML:
-        outputHtmlGadget(gadget, options, contentFilters, resp);
-        break;
-      case URL:
-        outputUrlGadget(gadget, options, resp);
-        break;
-    }
-  }
-
-  /**
-   * Handles type=html gadget output.
-   *
-   * @param gadget
-   * @param view
-   * @param options
-   * @param contentFilters
-   * @param resp
-   * @throws IOException
-   * @throws GadgetServer.GadgetProcessException
-   */
-  private void outputHtmlGadget(Gadget gadget,
-                                ProcessingOptions options,
-                                List<GadgetContentFilter> contentFilters,
-                                HttpServletResponse resp)
-      throws IOException, GadgetServer.GadgetProcessException {
-    resp.setContentType("text/html; charset=UTF-8");
-    StringBuilder markup = new StringBuilder();
-    View view = gadget.getView(options.getView());
-
-    // use single character for tab to simulate indentation within source code
-    /*String t = "  ";
-    String n = "\n";
-    if (!options.getDebug()) {
-      t = "";
-      n = "";
-    }*/
-
-    if (!view.getQuirks()) {
-      markup.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
-    }
-
-    markup.append("<html><head>");
-    // TODO: This is so wrong.
-    markup.append("<style type=\"text/css\">")
-          .append("body,td,div,span,p{font-family:arial,sans-serif;}")
-          .append("a {color:#0000cc;}a:visited {color:#551a8b;}")
-          .append("a:active {color:#ff0000;}")
-          .append("body{margin: 0px;padding: 0px;background-color:white;}")
-          .append("</style>");
-    markup.append("</head><body>");
-    StringBuilder externJs = new StringBuilder();
-    StringBuilder inlineJs = new StringBuilder();
-    String externFmt = "<script src=\"%s\"></script>";
-    String forcedLibs = options.getForcedJsLibs();
-
-    for (JsLibrary library : gadget.getJsLibraries()) {
-      JsLibrary.Type type = library.getType();
-      if (type == JsLibrary.Type.URL) {
-        // TODO: This case needs to be handled more gracefully by the js
-        // servlet. We should probably inline external JS as well.
-        externJs.append(String.format(externFmt, library.getContent()));
-      } else if (type == JsLibrary.Type.INLINE) {
-        inlineJs.append(library.getContent());
-      } else {
-        // FILE or RESOURCE
-        if (forcedLibs == null) {
-          if (options.getDebug()) {
-            inlineJs.append(library.getDebugContent());
-          } else {
-            inlineJs.append(library.getContent());
-          }
-        } // otherwise it was already included by options.forceJsLibs.
-      }
-    }
-
-    // Forced libs first.
-    if (forcedLibs != null) {
-      String[] libs = forcedLibs.split(":");
-      String jsUrl = servletState.getJsUrl(libs, options);
-      markup.append(String.format(externFmt, jsUrl));
-    } else {
-      appendJsConfig(options, gadget.getRequires().keySet(), inlineJs);
-    }
-
-    if (inlineJs.length() > 0) {
-      markup.append("<script><!--\n").append(inlineJs)
-            .append("\n-->\n</script>");
-    }
-
-    if (externJs.length() > 0) {
-      markup.append(externJs);
-    }
-
-    List<GadgetException> gadgetExceptions = new LinkedList<GadgetException>();
-    String content = gadget.getContentData(options.getView());
-    if (content == null) {
-      // unknown view
-      gadgetExceptions.add(
-          new GadgetException(
-              GadgetException.Code.UNKNOWN_VIEW_SPECIFIED,
-              "View: '" + options.getView() + "' invalid for gadget: " +
-              gadget.getId().getKey()));
-    } else {
-      for (GadgetContentFilter filter : contentFilters) {
-        try {
-          content = filter.filter(content);
-        } catch (GadgetException e) {
-          gadgetExceptions.add(e);
-        }
-      }
-    }
-
-    if (gadgetExceptions.size() > 0) {
-      throw new GadgetServer.GadgetProcessException(gadgetExceptions);
-    }
-
-    markup.append(content);
-    markup.append("<script>gadgets.util.runOnLoadHandlers();</script>");
-    markup.append("</body></html>");
-
-    resp.getWriter().print(markup.toString());
-  }
-
-  private void outputUrlGadget(Gadget gadget,  ProcessingOptions options,
-      HttpServletResponse resp) throws IOException {
-    // TODO: generalize this as injectedArgs on Gadget object
-
-    // Preserve existing query string parameters.
-    URI redirURI = gadget.getView(options.getView()).getHref();
-    String queryStr = redirURI.getQuery();
-    StringBuilder query = new StringBuilder(queryStr == null ? "" : queryStr);
-
-    // TODO: userprefs on the fragment rather than query string
-    query.append(getPrefsQueryString(gadget.getUserPrefValues()));
-
-    String[] libs;
-    String forcedLibs = options.getForcedJsLibs();
-    if (forcedLibs == null) {
-      Set<String> reqs = gadget.getRequires().keySet();
-      libs = reqs.toArray(new String[reqs.size()]);
-    } else {
-      libs = forcedLibs.split(":");
-    }
-    appendLibsToQuery(libs, query, options);
-
-    try {
-      redirURI = new URI(redirURI.getScheme(),
-                         redirURI.getUserInfo(),
-                         redirURI.getHost(),
-                         redirURI.getPort(),
-                         redirURI.getPath(),
-                         query.toString(),
-                         redirURI.getFragment());
-    } catch (URISyntaxException e) {
-      // Not really ever going to happen; input values are already OK.
-      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
-                     e.getMessage());
-    }
-    resp.sendRedirect(redirURI.toString());
-  }
-
-  private void outputErrors(GadgetServer.GadgetProcessException errs,
-                            HttpServletResponse resp)
-      throws IOException {
-    // TODO: make this way more robust
-    StringBuilder err = new StringBuilder();
-    for (GadgetException error : errs.getComponents()) {
-      err.append(error.getCode().toString());
-      err.append(' ');
-      err.append(error.getMessage());
-      err.append('\n');
-
-      // Log the errors here for now. We might want different severity levels
-      // for different error codes.
-      logger.log(Level.INFO, "Failed to render gadget", error);
-    }
-    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, err.toString());
-  }
-
-  @SuppressWarnings("unchecked")
-  private UserPrefs getPrefsFromRequest(HttpServletRequest req) {
-    Map<String, String> prefs = new HashMap<String, String>();
-    Enumeration<String> paramNames = req.getParameterNames();
-    while (paramNames.hasMoreElements()) {
-      String paramName = paramNames.nextElement();
-      if (paramName.startsWith(USERPREF_PARAM_PREFIX)) {
-        String prefName = paramName.substring(USERPREF_PARAM_PREFIX.length());
-        prefs.put(prefName, req.getParameter(paramName));
-      }
-    }
-    return new UserPrefs(prefs);
-  }
-
-  private String getPrefsQueryString(UserPrefs prefVals) {
-    StringBuilder buf = new StringBuilder();
-    for (Map.Entry<String, String> prefEntry : prefVals.getPrefs().entrySet()) {
-      buf.append('&');
-      try {
-        buf.append(USERPREF_PARAM_PREFIX)
-           .append(URLEncoder.encode(prefEntry.getKey(), "UTF8"))
-           .append("=")
-           .append(URLEncoder.encode(prefEntry.getValue(), "UTF8"));
-      } catch (UnsupportedEncodingException e) {
-        // If UTF8 is somehow not supported, we may as well bail.
-        // Not a whole lot we can do without such support.
-        throw new RuntimeException("Unexpected error: UTF8 not supported.");
-      }
-    }
-    return buf.toString();
-  }
-
-  /**
-   * Appends libs to the query string.
-   * @param libs
-   * @param query
-   * @param opts
-   */
-  private void appendLibsToQuery(
-      String[] libs, StringBuilder query, ProcessingOptions opts) {
-    query.append("&")
-         .append(LIBS_PARAM_NAME)
-         .append("=")
-         .append(servletState.getJsUrl(libs, opts));
-  }
-
-  /**
-   * @param req
-   * @return Whether or not to use caja.
-   */
-  protected boolean getUseCaja(HttpServletRequest req) {
-    String cajaParam = req.getParameter(CAJA_PARAM);
-    return "1".equals(cajaParam);
-  }
-
-  /**
-   * @param options
-   * @param reqs The features you require.
-   * @param js Existing js, to which the configuration will be appended.
-   */
-  private void appendJsConfig(
-      ProcessingOptions options, Set<String> reqs, StringBuilder js) {
-    // config *should* be handled by a feature, but unfortunately there's
-    // no way to make this feature always be the last item in the output.
-    // oh well.
-
-    GadgetServerConfigReader serverConfig
-        = servletState.getGadgetServer().getConfig();
-    SyndicatorConfig syndConf = serverConfig.getSyndicatorConfig();
-    JSONObject syndFeatures = syndConf.getJsonObject(options.getSyndicator(),
-                                                     "gadgets.features");
-    if (syndFeatures != null) {
-      // now we just want configuration for the features that we actually use.
-      // TODO: this is too much manual work, and we should probably just
-      // modify the gadget object to keep the list of transitive dependencies
-      Set<Entry> found = new HashSet<Entry>();
-      Set<String> miss = new HashSet<String>();
-      serverConfig.getFeatureRegistry().getIncludedFeatures(reqs, found, miss);
-
-      Set<String> features = new HashSet<String>(found.size());
-      for (Entry entry : found) {
-        features.add(entry.getName());
-      }
-      String[] featArray = features.toArray(new String[features.size()]);
-
-      try {
-        JSONObject featureConfig = new JSONObject(syndFeatures, featArray);
-        js.append("gadgets.config.init(")
-          .append(featureConfig.toString())
-          .append(");");
-      } catch (JSONException e) {
-        // shouldn't ever happen since we've already validated our JSON output.
-        throw new RuntimeException(e);
-      }
-    }
-  }
-
-  /**
-   * Validates that the parent parameter was acceptable.
-   *
-   * @param request
-   * @return True if the parent parameter is valid for the current
-   *     syndicator.
-   */
-  private boolean validateParent(HttpServletRequest request) {
-    String syndicator = request.getParameter("synd");
-    if (syndicator == null) {
-      syndicator = SyndicatorConfig.DEFAULT_SYNDICATOR;
-    }
-
-    String parent = request.getParameter("parent");
-
-    if (parent == null) {
-      // If there is no parent parameter, we are still safe because no
-      // dependent code ever has to trust it anyway.
-      return true;
-    }
-
-    SyndicatorConfig syndConf
-        = servletState.getGadgetServer().getConfig().getSyndicatorConfig();
-
-    try {
-      JSONArray parents = syndConf.getJsonArray(syndicator, "gadgets.parent");
-
-      if (parents == null) {
-        return true;
-      } else {
-        // We need to check each possible parent parameter against this regex.
-        for (int i = 0, j = parents.length(); i < j; ++i) {
-          // TODO: Should patterns be cached? Recompiling every request
-          // seems wasteful.
-          if (Pattern.matches(parents.getString(i), parent)) {
-            return true;
-          }
-        }
-      }
-    } catch (JSONException e) {
-      logger.log(Level.WARNING, "Configuration error", e);
-    }
-    return false;
+    GadgetRenderer renderer = new GadgetRenderer(req, resp, state);
+    renderer.process();
   }
 }

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpGadgetContext.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpGadgetContext.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpGadgetContext.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpGadgetContext.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,259 @@
+/*
+ * 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.http;
+
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.RenderingContext;
+import org.apache.shindig.gadgets.UserPrefs;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Implements GadgetContext using an HttpServletRequest
+ */
+public class HttpGadgetContext extends GadgetContext {
+
+  public static final String USERPREF_PARAM_PREFIX = "up_";
+
+  private final URI url;
+  @Override
+  public URI getUrl() {
+    if (url == null) {
+      return super.getUrl();
+    }
+    return url;
+  }
+
+  private final Integer moduleId;
+  @Override
+  public int getModuleId() {
+    if (moduleId == null) {
+      return super.getModuleId();
+    }
+    return moduleId;
+  }
+
+
+  private final Locale locale;
+  @Override
+  public Locale getLocale() {
+    if (locale == null) {
+      return super.getLocale();
+    }
+    return locale;
+  }
+
+  /**
+   * @param req
+   * @return The ignore cache setting, if appropriate params are set, or null.
+   */
+  private static URI getUrl(HttpServletRequest req) {
+    String url = req.getParameter("url");
+    if (url == null) {
+      return null;
+    }
+    try {
+      return new URI(url);
+    } catch (URISyntaxException e) {
+      return null;
+    }
+  }
+
+  /**
+   * @param req
+   * @return module id, if specified
+   */
+  private static Integer getModuleId(HttpServletRequest req) {
+    String mid = req.getParameter("mid");
+    if (mid == null) {
+      return null;
+    }
+    return Integer.parseInt(mid);
+  }
+
+
+  /**
+   * @param req
+   * @return The locale, if appropriate parameters are set, or null.
+   */
+  private static Locale getLocale(HttpServletRequest req) {
+    String language = req.getParameter("lang");
+    String country = req.getParameter("country");
+    if (language == null && country == null) {
+      return null;
+    } else if (language == null) {
+      language = "all";
+    } else if (country == null) {
+      country = "ALL";
+    }
+    return new Locale(language, country);
+  }
+
+  private final RenderingContext renderingContext;
+  @Override
+  public RenderingContext getRenderingContext() {
+    if (renderingContext == null) {
+      return super.getRenderingContext();
+    }
+    return renderingContext;
+  }
+
+  /**
+   * @param req
+   * @return The rendering context, if appropriate params are set, or null.
+   */
+  private static RenderingContext getRenderingContext(HttpServletRequest req) {
+    String c = req.getParameter("c");
+    if (c == null) {
+      return null;
+    }
+    return c.equals("1") ? RenderingContext.CONTAINER : RenderingContext.GADGET;
+  }
+
+  private final Boolean ignoreCache;
+  @Override
+  public boolean getIgnoreCache() {
+    if (ignoreCache == null) {
+      return super.getIgnoreCache();
+    }
+    return ignoreCache;
+  }
+
+  /**
+   * @param req
+   * @return The ignore cache setting, if appropriate params are set, or null.
+   */
+  private static Boolean getIgnoreCache(HttpServletRequest req) {
+    String ignoreCache = req.getParameter("nocache");
+    if (ignoreCache == null) {
+      return null;
+    } else if ("0".equals(ignoreCache)) {
+      return Boolean.FALSE;
+    }
+    return Boolean.TRUE;
+  }
+
+  private final String syndicator;
+  @Override
+  public String getSyndicator() {
+    if (syndicator == null) {
+      return super.getSyndicator();
+    }
+    return syndicator;
+  }
+
+  /**
+   * @param req
+   * @return The syndicator, if set, or null.
+   */
+  private static String getSyndicator(HttpServletRequest req) {
+    return req.getParameter("synd");
+  }
+
+  private final Boolean debug;
+  @Override
+  public boolean getDebug() {
+    if (debug == null) {
+      return super.getDebug();
+    }
+    return debug;
+  }
+
+  /**
+   * @param req
+   * @return Debug setting, if set, or null.
+   */
+  private static Boolean getDebug(HttpServletRequest req) {
+    String debug = req.getParameter("debug");
+    if (debug == null) {
+      return null;
+    } else if ("0".equals(debug)) {
+      return Boolean.FALSE;
+    }
+    return Boolean.TRUE;
+  }
+
+  private final String view;
+  @Override
+  public String getView() {
+    if (view == null) {
+      return super.getView();
+    }
+    return view;
+  }
+
+  /**
+   * @param req
+   * @return The view, if specified, or null.
+   */
+  private static String getView(HttpServletRequest req) {
+    return req.getParameter("view");
+  }
+
+  private final UserPrefs userPrefs;
+  @Override
+  public UserPrefs getUserPrefs() {
+    if (userPrefs == null) {
+      return super.getUserPrefs();
+    }
+    return userPrefs;
+  }
+
+  /**
+   * @param req
+   * @return UserPrefs, if any are set for this request.
+   */
+  @SuppressWarnings("unchecked")
+  private static UserPrefs getUserPrefs(HttpServletRequest req) {
+    Map<String, String> prefs = new HashMap<String, String>();
+    Enumeration<String> paramNames = req.getParameterNames();
+    if (paramNames == null) {
+      return null;
+    }
+    while (paramNames.hasMoreElements()) {
+      String paramName = paramNames.nextElement();
+      if (paramName.startsWith(USERPREF_PARAM_PREFIX)) {
+        String prefName = paramName.substring(USERPREF_PARAM_PREFIX.length());
+        String escapedParam =
+        prefs.put(prefName, req.getParameter(paramName));
+      }
+    }
+    return new UserPrefs(prefs);
+  }
+
+  public HttpGadgetContext(HttpServletRequest request) {
+    url = getUrl(request);
+    moduleId = getModuleId(request);
+    locale = getLocale(request);
+    renderingContext = getRenderingContext(request);
+    ignoreCache = getIgnoreCache(request);
+    syndicator = getSyndicator(request);
+    debug = getDebug(request);
+    view = getView(request);
+    userPrefs = getUserPrefs(request);
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java?rev=635862&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java Tue Mar 11 02:52:52 2008
@@ -0,0 +1,87 @@
+/*
+ * 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.http;
+
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.SyndicatorConfig;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Set;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Collection of HTTP utilities
+ */
+public class HttpUtil {
+  public static final long START_TIME = System.currentTimeMillis();
+  // 1 year.
+  private static final int DEFAULT_TTL = 60 * 60 * 24 * 365;
+
+  /**
+   * Sets HTTP headers that instruct the browser to cache indefinitely.
+   * Implementations should take care to use cache-busting techniques on the
+   * url.
+   *
+   * @param response The HTTP response
+   * @param ttl The time to cache for, in seconds. If 0, DEFAULT_TTL will
+   *   be used.
+   */
+  public static void setCachingHeaders(HttpServletResponse response, int ttl) {
+    if (ttl == 0) {
+      ttl = DEFAULT_TTL;
+    }
+
+    response.setDateHeader("Expires",
+        System.currentTimeMillis() + (1000L * ttl));
+
+    // IE seems to need this (10 years should be enough).
+    response.setHeader("Cache-Control", "public,max-age=" +
+        Integer.toString(ttl));
+
+    // Firefox requires this for certain cases.
+    response.setDateHeader("Last-Modified", START_TIME);
+  }
+
+  /**
+   * Fetches js configuration for the given feature set & syndicator
+   * @param config
+   * @param context
+   * @param features
+   */
+  public static String getJsConfig(SyndicatorConfig config,
+      GadgetContext context, Set<String> features) {
+    JSONObject syndFeatures = config.getJsonObject(context.getSyndicator(),
+                                                   "gadgets.features");
+    if (syndFeatures != null) {
+      String[] featArray = features.toArray(new String[features.size()]);
+      try {
+        JSONObject featureConfig = new JSONObject(syndFeatures, featArray);
+        return "\ngadgets.config.init(" + featureConfig.toString() +
+               (context.getDebug() ? ");" : ", true);");
+      } catch (JSONException e) {
+        return "";
+      }
+    }
+    return "";
+  }
+}