You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by jo...@apache.org on 2007/12/12 11:33:38 UTC

svn commit: r603539 [1/3] - in /incubator/shindig/trunk: ./ java/ java/gadgets/ java/gadgets/src/ java/gadgets/src/main/ java/gadgets/src/main/java/ java/gadgets/src/main/java/org/ java/gadgets/src/main/java/org/apache/ java/gadgets/src/main/java/org/a...

Author: johnh
Date: Wed Dec 12 02:33:35 2007
New Revision: 603539

URL: http://svn.apache.org/viewvc?rev=603539&view=rev
Log:
Initial commit of Shindig Gadget Container JavaScript and Gadget Server.

Includes:

  * Gadget Container JavaScript. Code embedded into an arbitrary web page
    enabling it to render and manage Gadgets.
    + Support for rendering using gmodules.com or a Shindig Gadget Server
    + Basic IFPC (gadget <-> container communication) support via inclusion
      of Google IFPC library
    + Simple layout management: render in DIV (StaticLayoutManager);
      render multiple Gadgets left-aligned in DIV (FloatLeftLayoutManager)
    + UserPrefs support through JS interface
    + Cookie-based UserPrefs implementation
    + _IG_SetTitle container-side support
    + _IG_AdjustIframeHeight container-side support
    + Sample pages demonstrating all the above functionality

  * Gadget Server (written in Java). Web server that processes Gadget
    requests, parsing their spec XML, processing it through a workflow
    of core and extension (<Require>/<Optional>) Features, and serializing
    output for rendering the Gadget to the end user.
    + Core server implementation constructing processing workflows out of
      GadgetFeature objects providing various feature support
    + GadgetFeature interface providing core extensibility mechanism for
      the Gadgets platform; extenders implement GadgetFeature and register
      their implementation with GadgetFeatureRegistry
    + Gadget spec XML parser
    + Simple caching API with in-memory Map-based implementations as samples
    + Remote content (eg. HTTP) fetcher interface
    + Basic java.net-based RemoteContentFetcher implementation
    + "Hangman" variable substitution support (eg. __MSG_foo__), including BIDI,
      MSG (message bundles) and UP (user prefs) type variables
    + Early JavaScript-based Feature support through JsLibrary* classes
    + Content proxy including JSON support

  * A handful of tests for various Server components.

  * Maven build support.

This submission represents significant contributions by:
  fargo@google.com, etnu@google.com, dharkness@google.com,
  jyang@google.com, wangz@google.com, dcoker@google.com

Much more to come!


Added:
    incubator/shindig/trunk/COPYING
    incubator/shindig/trunk/NOTICE
    incubator/shindig/trunk/README
    incubator/shindig/trunk/java/
    incubator/shindig/trunk/java/gadgets/
    incubator/shindig/trunk/java/gadgets/README
    incubator/shindig/trunk/java/gadgets/pom.xml
    incubator/shindig/trunk/java/gadgets/src/
    incubator/shindig/trunk/java/gadgets/src/main/
    incubator/shindig/trunk/java/gadgets/src/main/java/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetDataCache.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicRemoteContentFetcher.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BidiSubstituter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/CoreJsFeature.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetContext.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetDataCache.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeatureRegistry.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/GadgetSpec.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpecParser.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetView.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibraryFeature.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundle.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleParser.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/MessageBundleSubstituter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ModuleSubstituter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContent.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentFetcher.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SpecParserException.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Substitutions.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefSubstituter.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/UserPrefs.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Utf8InputStream.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/AnalyticsFeature.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/BasicGadgetHttpServlet.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/BasicHttpContext.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/JsonpProxyServlet.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyServlet.java
    incubator/shindig/trunk/java/gadgets/src/main/webapp/
    incubator/shindig/trunk/java/gadgets/src/main/webapp/WEB-INF/
    incubator/shindig/trunk/java/gadgets/src/main/webapp/WEB-INF/web.xml
    incubator/shindig/trunk/java/gadgets/src/test/
    incubator/shindig/trunk/java/gadgets/src/test/java/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/EasyMockTestCase.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetSpecTestFixture.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SubstitutionsTest.java
    incubator/shindig/trunk/javascript/
    incubator/shindig/trunk/javascript/README
    incubator/shindig/trunk/javascript/container/
    incubator/shindig/trunk/javascript/container/cookies.js
    incubator/shindig/trunk/javascript/container/gadgets.css
    incubator/shindig/trunk/javascript/container/gadgets.js
    incubator/shindig/trunk/javascript/container/ifpc_relay.html
    incubator/shindig/trunk/javascript/container/json.js
    incubator/shindig/trunk/javascript/container/sample1.html
    incubator/shindig/trunk/javascript/container/sample2.html
    incubator/shindig/trunk/javascript/container/sample3.html
    incubator/shindig/trunk/javascript/container/sample4.html

Added: incubator/shindig/trunk/COPYING
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/COPYING?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/COPYING (added)
+++ incubator/shindig/trunk/COPYING Wed Dec 12 02:33:35 2007
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2007 Google Inc.
+
+   Licensed 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.

Added: incubator/shindig/trunk/NOTICE
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/NOTICE?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/NOTICE (added)
+++ incubator/shindig/trunk/NOTICE Wed Dec 12 02:33:35 2007
@@ -0,0 +1,2 @@
+This product includes software (Gadget Server, Gadget Container) developed by
+Google Inc. (http://code.google.com/)

Added: incubator/shindig/trunk/README
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/README?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/README (added)
+++ incubator/shindig/trunk/README Wed Dec 12 02:33:35 2007
@@ -0,0 +1,10 @@
+Welcome to Apache Shindig!
+
+* Read java/gadgets/README for instructions on how to start up your own
+  Shindig Gadget Server.
+
+* Read javascript/README for instructions for using the Shindig Gadget
+  Container JavaScript to enable your page to render Gadgets using
+  gmodules.com or a server started up as described above.
+
+For more information, see http://incubator.apache.org/projects/shindig.html

Added: incubator/shindig/trunk/java/gadgets/README
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/README?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/README (added)
+++ incubator/shindig/trunk/java/gadgets/README Wed Dec 12 02:33:35 2007
@@ -0,0 +1,16 @@
+Installing and Running Shindig Gadget Server
+============================================
+
+1) Install Maven 2.0 (see http://maven.apache.org)
+
+2) Make sure the JAVA_HOME environment variable is set to the location of your
+   JDK/JRE, and that the maven executable is in your PATH.
+
+3) From java/gadgets/...
+   * mvn package - Builds Gadget Server and runs tests.
+   * mvn jetty:run - Builds Gadget Server (no tests), installs Servlet at localhost:8080.
+
+4) Hit server at http://localhost:8080/gadgets/ifr?url=<gadget-url>
+   Example: http://localhost:8080/gadgets/ifr?url=http://www.labpixies.com/campaigns/todo/todo.xml
+
+For more information, see http://incubator.apache.org/projects/shindig.html

Added: incubator/shindig/trunk/java/gadgets/pom.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/pom.xml?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/pom.xml (added)
+++ incubator/shindig/trunk/java/gadgets/pom.xml Wed Dec 12 02:33:35 2007
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<!--
+
+  Licensed 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.shindig</groupId>
+  <artifactId>gadgets</artifactId>
+  <version>0.5</version>
+  <packaging>war</packaging>
+
+  <name>Shindig Gadget Server</name>
+  <url>http://shindig.apache.org</url>
+
+  <licenses>
+    <license>
+      <name>Apache 2</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+    </license>
+  </licenses>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.json</groupId>
+      <artifactId>json</artifactId>
+      <version>20070829</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>2.3</version>
+      <scope>compile</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <version>2.3</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymockclassextension</artifactId>
+      <version>2.2.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <finalName>gadgets</finalName>
+    <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
+    <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory>
+    <outputDirectory>${basedir}/target/classes</outputDirectory>
+    <testOutputDirectory>${basedir}/target/test-classes</testOutputDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+          <configuration>
+            <source>1.5</source>
+            <target>1.5</target>
+         </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.mortbay.jetty</groupId>
+        <artifactId>maven-jetty-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+</project>

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetDataCache.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetDataCache.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetDataCache.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetDataCache.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,39 @@
+/*
+ * Licensed 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.GadgetDataCache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Basic implementation of an in-memory data cache, backed by a
+ * {@code HashMap}.
+ * 
+ * @param <T> Type of data to store in the cache.
+ */
+public class BasicGadgetDataCache<T> implements GadgetDataCache<T> {
+  private Map<String, T> cache = new HashMap<String, T>();
+
+  /** {@inheritDoc} */
+  public T get(String key) {
+    return cache.get(key);
+  }
+
+  /** {@inheritDoc} */
+  public void put(String key, T value) {
+    cache.put(key, value);
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicRemoteContentFetcher.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicRemoteContentFetcher.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicRemoteContentFetcher.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicRemoteContentFetcher.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,73 @@
+/*
+ * Licensed 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.RemoteContentFetcher;
+import org.apache.shindig.gadgets.RemoteContent;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of a {@code RemoteObjectFetcher} using standard java.net
+ * classes. Only supports HTTP fetching at present.
+ */
+public class BasicRemoteContentFetcher implements RemoteContentFetcher {
+  private static final int CONNECT_TIMEOUT_MS = 5000;
+
+  private final int maxObjSize;
+
+  /**
+   * Creates a new fetcher capable of retrieving objects {@code maxObjSize}
+   * bytes or smaller in size.
+   * @param maxObjSize Maximum size, in bytes, of object to fetch
+   */
+  public BasicRemoteContentFetcher(int maxObjSize) {
+    this.maxObjSize = maxObjSize;
+  }
+
+  /** {@inheritDoc} */
+  public RemoteContent fetch(URL url) {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+    int responseCode;
+    HttpURLConnection fetcher;
+    Map<String, List<String>> headers = null;
+
+    try {
+      fetcher = (HttpURLConnection) url.openConnection();
+      fetcher.setInstanceFollowRedirects(true);
+      fetcher.setConnectTimeout(CONNECT_TIMEOUT_MS);
+
+      responseCode = fetcher.getResponseCode();
+      headers = fetcher.getHeaderFields();
+
+      byte chunk[] = new byte[8192];
+      int chunkSize;
+      InputStream in = fetcher.getInputStream();
+      while (out.size() < maxObjSize && (chunkSize = in.read(chunk)) != -1) {
+        out.write(chunk, 0, chunkSize);
+      }
+    } catch (IOException e) {
+      responseCode = 500;
+    }
+
+    return new RemoteContent(responseCode, out.toByteArray(), headers);
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BidiSubstituter.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BidiSubstituter.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BidiSubstituter.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BidiSubstituter.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,86 @@
+/*
+ * Licensed 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 java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Provides static hangman substitutions for bidirectional language support.
+ * Useful for generating internationalized layouts using CSS.
+ */
+public class BidiSubstituter implements GadgetFeature {
+
+  /**
+   * Fetches a message bundle spec from the {@code GadgetSpec} for the
+   * provided locale.
+   * @param spec Gadget spec from which to retrieve message bundle spec
+   * @param locale Locale of message bundle to retrieve
+   * @return The message bundle, or null if not found
+   */
+  private GadgetSpec.MessageBundle getBundle(GadgetSpec spec,
+                                             Locale locale) {
+    List<GadgetSpec.MessageBundle> bundles = spec.getMessageBundles();
+    for (GadgetSpec.MessageBundle bundle : bundles) {
+      if (bundle.getLocale().equals(locale)) {
+        return bundle;
+      }
+    }
+    return null;
+  }
+
+  /** {@inheritDoc} */
+  public void prepare(GadgetView spec,
+                      GadgetContext context,
+                      Map<String, String> params) {
+    // Nothing here.
+  }
+
+  /**
+   * Populates bidi substitutions.
+   * @param gadget Gadget object to process
+   * @param context Context in which Gadget is being processed
+   */
+  public void process(Gadget gadget,
+                      GadgetContext context,
+                      Map<String, String> params) {
+    Substitutions subst = gadget.getSubstitutions();
+    Locale locale = context.getLocale();
+    // Find an appropriate bundle for the ltr flag.
+    GadgetSpec.MessageBundle bundle = getBundle(gadget, locale);
+    if (null == bundle) {
+      bundle = getBundle(gadget, new Locale(locale.getLanguage(), "all"));
+    }
+    if (null == bundle) {
+      bundle = getBundle(gadget, new Locale("all", "all"));
+    }
+    boolean rtl = false;
+    if (bundle != null) {
+      rtl = bundle.isRightToLeft();
+    }
+    subst.addSubstitution(Substitutions.Type.BIDI,
+                          "START_EDGE",
+                          rtl ? "right" : "left");
+    subst.addSubstitution(Substitutions.Type.BIDI,
+                          "END_EDGE",
+                          rtl ? "left" : "right");
+    subst.addSubstitution(Substitutions.Type.BIDI,
+                          "DIR",
+                          rtl ? "rtl" : "ltr");
+    subst.addSubstitution(Substitutions.Type.BIDI,
+                          "REVERSE_DIR",
+                          rtl ? "ltr" : "rtl");
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/CoreJsFeature.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/CoreJsFeature.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/CoreJsFeature.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/CoreJsFeature.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,27 @@
+/*
+ * Licensed 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 java.util.Map;
+
+/**
+ * Adds all core javascript dependencies that are needed by all gadgets.
+ */
+public class CoreJsFeature extends JsLibraryFeature {
+  @Override
+  public void process(Gadget gadget, GadgetContext context,
+      Map<String, String> params) {
+    gadget.addJsLibrary(JsLibrary.file("http://gmodules.com/ig/extern_js/f/CgJlbhICdXMrMAE4ACw/6gZqwg2JpuM.js"));
+  }
+}
\ No newline at end of file

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,296 @@
+/*
+ * Licensed 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 java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Intermediary representation of all state associated with processing
+ * of a single gadget request.
+ *
+ * This class is constructed by an immutable base {@code GadgetSpec},
+ * and is modified in parallel by a number of {@code GadgetFeature}
+ * processors, in an order defined by their dependencies, in
+ * {@code GadgetServer}.
+ *
+ * Upon completion of processing, a {@code Gadget} is serialized as appropriate
+ * to whatever output format is appropriate (eg. as gadget content in an
+ * IFRAME), potentially with post-processing such as HTML whitespace
+ * compression or HTML+JS (Caja) rewriting applied.
+ *
+ * "Hangman" variable substitutions (eg. __MSG_foo__) are performed as needed
+ * and transparently for fields that support this functionality.
+ */
+public class Gadget implements GadgetView {
+  private final ID id;
+  private final GadgetSpec baseSpec;
+  private final Substitutions substitutions;
+  private final Map<String, String> userPrefValues;
+  private final List<JsLibrary> jsLibraries;
+
+  public static class GadgetId implements GadgetView.ID {
+    private final URL url;
+    private final int moduleId;
+
+    public GadgetId(URL url, int moduleId) {
+      this.url = url;
+      this.moduleId = moduleId;
+    }
+
+    @Override
+    public boolean equals(Object comp) {
+      if (comp instanceof GadgetView.ID) {
+        GadgetView.ID id = (GadgetView.ID)comp;
+        return id.getURL() == url &&
+               id.getModuleId() == moduleId;
+      } else {
+        return false;
+      }
+    }
+
+    @Override
+    public int hashCode() {
+      int result = 17;
+      result = (37 * result) + url.hashCode();
+      result = (37 * result) + moduleId;
+      return result;
+    }
+
+    public URL getURL() {
+      return url;
+    }
+
+    public int getModuleId() {
+      return moduleId;
+    }
+
+    public String getKey() {
+      return url.toString();
+    }
+  }
+
+  /**
+   * Create a new {@code Gadget} devoid of processing modifications.
+   * @param id Identifier used to retrieve {@code baseSpec}
+   * @param baseSpec Base (immutable) {@code GadgetSpec} on which this is based
+   */
+  public Gadget(ID id, GadgetSpec baseSpec) {
+    this.id = id;
+    this.baseSpec = baseSpec;
+    substitutions = new Substitutions();
+    userPrefValues = new HashMap<String, String>();
+    jsLibraries = new LinkedList<JsLibrary>();
+  }
+
+  /**
+   * @return Global identifier used to retrieve gadget's spec
+   */
+  public ID getId() {
+    return id;
+  }
+
+  /**
+   * @return GadgetSpec that backs this Gadget. Package scope for tests.
+   */
+  GadgetSpec getBaseSpec() {
+    return baseSpec;
+  }
+
+  /**
+   * @return Object containing all hangman substitutions applied to this gadget
+   */
+  public Substitutions getSubstitutions() {
+    return substitutions;
+  }
+
+  // GadgetSpec accessors
+
+  /**
+   * @return Gadget title with substitutions applied
+   */
+  public String getTitle() {
+    return substitutions.substitute(baseSpec.getTitle());
+  }
+
+  /**
+   * @return URL used as a target for Gadget's title link, or null if malformed
+   */
+  public URL getTitleURL() {
+    URL ret = null;
+    String urlStr = baseSpec.getTitleURL().toString();
+    try {
+      ret = new URL(substitutions.substitute(urlStr));
+    } catch (MalformedURLException e) {
+      return null;
+    }
+    return ret;
+  }
+
+  /**
+   * @return String used to describe this Gadget in directories, with
+   * substitutions applied
+   */
+  public String getDirectoryTitle() {
+    return substitutions.substitute(baseSpec.getDirectoryTitle());
+  }
+
+  /**
+   * @return Extended description of {@code Gadget}, with substitutions applied
+   */
+  public String getDescription() {
+    return substitutions.substitute(baseSpec.getDescription());
+  }
+
+  /**
+   * @return Name of this Gadget's author as specified in its spec
+   */
+  public String getAuthor() {
+    return baseSpec.getAuthor();
+  }
+
+  /**
+   * @return E-mail address of this Gadget's author as specified in its spec
+   */
+  public String getAuthorEmail() {
+    return baseSpec.getAuthorEmail();
+  }
+
+  // TODO: make this URL?
+  public String getScreenshot() {
+    return baseSpec.getScreenshot();
+  }
+
+  // TODO: make this URL?
+  public String getThumbnail() {
+    return baseSpec.getThumbnail();
+  }
+
+  public List<MessageBundle> getMessageBundles() {
+    return new ArrayList<MessageBundle>(baseSpec.getMessageBundles());
+  }
+
+  /**
+   * @return List of all {@code FeatureSpec}s declared by this gadget
+   */
+  public Map<String, FeatureSpec> getRequires() {
+    return Collections.unmodifiableMap(baseSpec.getRequires());
+  }
+
+  /**
+   * @return All JS libraries needed to render this gadget.
+   */
+  public List<JsLibrary> getJsLibraries() {
+    return Collections.unmodifiableList(jsLibraries);
+  }
+
+  /**
+   * @param library
+   */
+  public void addJsLibrary(JsLibrary library) {
+    jsLibraries.add(library);
+  }
+
+  /**
+   * Extracts parameters for the given feature.
+   *
+   * @param gadget
+   * @param feature
+   * @return The parameters, or an empty map.
+   */
+  @SuppressWarnings("unchecked")
+  public static Map<String, String> getFeatureParams(Gadget gadget,
+                                                     String feature) {
+    GadgetSpec.FeatureSpec spec = gadget.getRequires().get(feature);
+    if (spec == null) {
+      return Collections.EMPTY_MAP;
+    } else {
+      return spec.getParams();
+    }
+  }
+
+  /**
+   * @return List of all preload URLs declared, with substitutions applied
+   */
+  public List<String> getPreloads() {
+    List<String> ret = new LinkedList<String>();
+    for (String preload : baseSpec.getPreloads()) {
+      ret.add(substitutions.substitute(preload));
+    }
+    return ret;
+  }
+
+  /**
+   * @return List of icons defined in gadget spec
+   */
+  public List<Icon> getIcons() {
+    return Collections.unmodifiableList(baseSpec.getIcons());
+  }
+
+  /**
+   * @return List of all user pref specs defined in gadget spec
+   */
+  public List<UserPref> getUserPrefs() {
+    return Collections.unmodifiableList(baseSpec.getUserPrefs());
+  }
+
+  public Map<String, String> getUserPrefValues() {
+    return Collections.unmodifiableMap(userPrefValues);
+  }
+
+  /**
+   * @return Type of gadget to render
+   */
+  public ContentType getContentType() {
+    return baseSpec.getContentType();
+  }
+
+  /**
+   * @return URL of gadget to render of type == URL; null if malformed/missing
+   */
+  public URL getContentHref() {
+    if (getContentType() != ContentType.URL) {
+      return null;
+    }
+
+    URL ret = null;
+    String urlStr = baseSpec.getContentHref().toString();
+    try {
+      ret = new URL(substitutions.substitute(urlStr));
+    } catch (MalformedURLException e) {
+      return null;
+    }
+    return ret;
+  }
+
+  /**
+   * @return Gadget contents with all substitutions applied
+   */
+  public String getContentData() {
+    return substitutions.substitute(baseSpec.getContentData());
+  }
+
+  /**
+   * @return Copy of base spec that created this {@code Gadget}
+   */
+  public GadgetSpec copy() {
+    return baseSpec.copy();
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetContext.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetContext.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetContext.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetContext.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,51 @@
+/*
+ * Licensed 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 java.util.Locale;
+
+/**
+ * Bundles together per-server data and helper mechanisms providing
+ * generic functionality such as retrieval of remote data and caching.
+ */
+public class GadgetContext {
+  private final RemoteContentFetcher httpFetcher;
+  public RemoteContentFetcher getHttpFetcher() {
+    return httpFetcher;
+  }
+
+  private final GadgetDataCache<MessageBundle> messageBundleCache;
+  public GadgetDataCache<MessageBundle> getMessageBundleCache() {
+    return messageBundleCache;
+  }
+
+  private final Locale locale;
+  public Locale getLocale() {
+    return locale;
+  }
+
+  /**
+   * Creates a context for the current gadget.
+   * @param httpFetcher
+   * @param messageBundleCache
+   * @param locale
+   */
+  public GadgetContext(RemoteContentFetcher httpFetcher,
+                       GadgetDataCache<MessageBundle> messageBundleCache,
+                       Locale locale) {
+    this.httpFetcher = httpFetcher;
+    this.messageBundleCache = messageBundleCache;
+    this.locale = locale;
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetDataCache.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetDataCache.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetDataCache.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetDataCache.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,24 @@
+/*
+ * Licensed 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;
+
+/**
+ * Simple interface for providing a data cache for objects of type T keyed
+ * by a String.
+ * @param <T> Type of data to store in the cache
+ */
+public interface GadgetDataCache<T> {
+  public T get(String key) throws GadgetException;
+  public void put(String key, T val) throws GadgetException;
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,62 @@
+/*
+ * Licensed 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;
+
+/**
+ * Base class for all Gadget exceptions. The bulk of the code uses
+ * this class directly, differentiating between error conditions by
+ * the Code enumeration.
+ */
+public class GadgetException extends Exception {
+  public static enum Code {
+    // Catch-all for internal errors
+    INTERNAL_SERVER_ERROR,
+
+    // General xml
+    EMPTY_XML_DOCUMENT,
+    MALFORMED_XML_DOCUMENT,
+
+    FAILED_TO_RETRIEVE_CONTENT,
+
+    UNSUPPORTED_FEATURE,
+
+    // Interface component errors.
+    MISSING_SPEC_CACHE,
+    MISSING_MESSAGE_BUNDLE_CACHE,
+    MISSING_REMOTE_OBJECT_FETCHER,
+    
+    // Caja error
+    MALFORMED_FOR_SAFE_INLINING
+  }
+
+  private final Code code;
+
+  public GadgetException(Code code) {
+    this.code = code;
+  }
+  
+  public GadgetException(Code code, Throwable cause) {
+    super(cause);
+    this.code = code;
+  }
+  
+  public GadgetException(Code code, String msg) {
+    super(msg);
+    this.code = code;
+  }
+
+  public Code getCode() {
+    return code;
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 java.util.Map;
+
+/**
+ * Base interface providing Gadget Server's primary extensibility mechanism.
+ * 
+ * During processing of a {@code Gadget}, a tree of {@code GadgetFeature}
+ * objects is constructed based on the &lt;Require&gt; and &lt;Optional&gt;
+ * tags declared in its {@code GadgetSpec}, and the dependencies registered
+ * for these in {@code GadgetFeatureRegistry}.
+ * 
+ * Each {@code GadgetFeature}'s prepare method is called first - potentially
+ * in parallel with many others whose dependencies have also been satisfied.
+ * Once this has completed, its process method is called. Prepare is useful
+ * for async operations such as retrieval of a remote resource; all
+ * {@code Gadget} modifications occur in process.
+ * 
+ * To extend the Gadget Server's feature set, simply implement this interface
+ * and register your class with {@code GadgetFeatureRegistry}, indicating
+ * which other {@code GadgetFeature} features are needed before yours can
+ * operate successfully.
+ * 
+ * Each feature <i>must</i> be instantiable by a no-argument constructor,
+ * and will <i>always</i> be instantiated this way. As such, it is recommended
+ * not to define a constructor for a feature at all.
+ */
+public interface GadgetFeature {
+  public void prepare(GadgetView gadget, GadgetContext context,
+                      Map<String, String> params) throws GadgetException;
+  public void process(Gadget gadget, GadgetContext context,
+                      Map<String, String> params) throws GadgetException;
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeatureRegistry.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeatureRegistry.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeatureRegistry.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeatureRegistry.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,195 @@
+/*
+ * Licensed 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 java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Maintains a registry of all {@code GadgetFeature} types supported by
+ * a given Gadget Server installation.
+ *
+ * To register a feature, simply create a static initialization block in its
+ * class definition:
+ * <pre>
+ *   static {
+ *     GadgetFeatureRegistry.register("my.feature.name",
+ *                                    { "my.dep1", "my.dep2" },
+ *                                    MyFeature.class);
+ *   }
+ * </pre>
+ */
+public class GadgetFeatureRegistry {
+  private static final Map<String, Entry> features =
+      new HashMap<String, Entry>();
+  private static final List<Entry> core =
+      new LinkedList<Entry>();
+
+  // Initialization of core components, providing a minimal base context
+  // in which all Gadgets operate. This set should be kept as minimal as
+  // possible. Anything added as registerCore will automatically become a
+  // dependency of every other feature.
+  static {
+    // Substitution jobs are not order-dependent, because the actual order that
+    // they are evaluated in is determined in Substitutions.java. The order
+    // defined here is not important.
+    registerCore("core.msgbundlesubst", null, MessageBundleSubstituter.class);
+    registerCore("core.bidisubst", null, BidiSubstituter.class);
+    registerCore("core.modulesubst", null, ModuleSubstituter.class);
+    registerCore("core.userprefsubst", null, UserPrefSubstituter.class);
+
+    // Core JS loading.
+    registerCore("core.js", null, CoreJsFeature.class);
+
+    // These are all satisfied by core.
+    register("setprefs", null, NoOpFeature.class);
+  }
+
+  /**
+   * Register a {@code GadgetFeature} identified by {@code name} which
+   * depends on other {@code GadgetFeature}s listed in {@code deps}
+   * completing before this one does.
+   *
+   * Names are freeform, but it is strongly suggested that they are
+   * namespaced, optionally (yet often usefully) in Java package-notation ie.
+   * 'com.google.gadgets.skins'.
+   *
+   * @param name Name of the feature to register, ideally using the conventions
+   * described
+   * @param deps List of strings indicating features on which {@code feature}
+   * depends to operate correctly, which need to process the {@code Gadget}
+   * before it does
+   * @param feature Class implementing the feature
+   */
+  public static void register(String name,
+                              String[] deps,
+                              Class<? extends GadgetFeature> feature) {
+    // Core entries must come first.
+    Entry entry = new Entry(name, deps, feature);
+    for (Entry coreEntry : core) {
+      entry.deps.add(coreEntry.getName());
+    }
+    features.put(name, entry);
+    validateFeatureGraph();
+  }
+
+  /**
+   * Registers a {@code GadgetFeature} which is <i>always</i> run when
+   * processing a {@code Gadget}, and on which all other features
+   * implicitly depend. Use of this mechanism should be as sparing as possible
+   * to optimize performance.
+   *
+   * @param deps
+   * @param cap
+   */
+  private static void registerCore(String name,
+                                   String[] deps,
+                                   Class<? extends GadgetFeature> cap) {
+    core.add(new Entry(name, deps, cap));
+    validateFeatureGraph();
+  }
+
+  /**
+   * Traverses the graph traversed by the registered features, validating
+   * that it comprises a directed acyclic graph in which all features'
+   * dependencies are provided.
+   *
+   * If the graph is not acyclic, it cannot be used to create a workflow. If
+   * any dependencies are missing, {@code Gadget} rendering may be incomplete.
+   */
+  private static void validateFeatureGraph() {
+    // TODO: ensure that features form a DAG and that all deps are provided
+  }
+
+  /**
+   * Attempts to retrieve all the {@code GadgetFeature} classes specified
+   * in the {@code needed} list. Those that are found are returned in
+   * {@code resultsFound}, while the names of those that are missing are
+   * populated in {@code resultsMissing}.
+   * @param needed List of names identifying features to retrieve
+   * @param resultsFound List of feature entries found
+   * @param resultsMissing List of feature identifiers that could not be found
+   * @return True if all features were retrieved
+   */
+  public static boolean getIncludedFeatures(List<String> needed,
+                                            List<Entry> resultsFound,
+                                            List<String> resultsMissing) {
+    resultsFound.clear();
+    resultsMissing.clear();
+    resultsFound.addAll(core);
+    for (String featureName : needed) {
+      Entry entry = features.get(featureName);
+      if (entry == null) {
+        resultsMissing.add(featureName);
+      }
+      resultsFound.add(entry);
+    }
+    return resultsMissing.size() == 0;
+  }
+
+  public static class NoOpFeature implements GadgetFeature {
+    public void prepare(GadgetView gadget, GadgetContext context,
+        Map<String, String> params) {
+    }
+    public void process(Gadget gadget, GadgetContext context,
+        Map<String, String> params) {
+    }
+  }
+
+  /**
+   * Ties together a {@code GadgetFeature} with its name and dependencies.
+   */
+  public static class Entry {
+    private final String name;
+    private final Set<String> deps;
+    private final Class<? extends GadgetFeature> feature;
+
+    private Entry(String name,
+                  String[] deps,
+                  Class<? extends GadgetFeature> feature) {
+      this.name = name;
+      this.deps = new HashSet<String>();
+      if (deps != null) {
+        this.deps.addAll(Arrays.asList(deps));
+      }
+      this.feature = feature;
+    }
+
+    /**
+     * @return Name identifier
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * @return List of identifiers on which feature depends
+     */
+    public List<String> getDependencies() {
+      return new LinkedList<String>(deps);
+    }
+
+    /**
+     * @return Class implementing the feature
+     */
+    public Class<? extends GadgetFeature> getFeature() {
+      return feature;
+    }
+  }
+}

Added: 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=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,484 @@
+/*
+ * Licensed 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.Future;
+
+public class GadgetServer {
+  private final Executor executor;
+  private GadgetDataCache<GadgetSpec> specCache;
+  private GadgetDataCache<MessageBundle> messageBundleCache;
+  private RemoteContentFetcher fetcher;
+
+  public GadgetServer(Executor executor) {
+    this.executor = executor;
+  }
+
+  public void setSpecCache(GadgetDataCache<GadgetSpec> specCache) {
+    this.specCache = specCache;
+  }
+
+  public void setMessageBundleCache(GadgetDataCache<MessageBundle> cache) {
+    messageBundleCache = cache;
+  }
+
+  public void setContentFetcher(RemoteContentFetcher fetcher) {
+    this.fetcher = fetcher;
+  }
+
+  public static enum RenderingContext {
+    CONTAINER, GADGET
+  }
+
+  public Gadget processGadget(Gadget.ID gadgetId,
+                              UserPrefs userPrefs,
+                              Locale locale,
+                              RenderingContext rctx)
+      throws GadgetProcessException {
+    if (specCache == null) {
+      throw new GadgetProcessException(GadgetException.Code.MISSING_SPEC_CACHE);
+    }
+    if (messageBundleCache == null ) {
+      throw new GadgetProcessException(
+          GadgetException.Code.MISSING_MESSAGE_BUNDLE_CACHE);
+    }
+    if (fetcher == null) {
+      throw new GadgetProcessException(
+          GadgetException.Code.MISSING_REMOTE_OBJECT_FETCHER);
+    }
+
+    // Queue/tree of all jobs to be run for successful processing
+    GadgetContext gc = new GadgetContext(fetcher, messageBundleCache, locale);
+    WorkflowContext wc = new WorkflowContext(gc);
+
+    // Bootstrap tree of jobs to process
+    WorkflowDependency cacheLoadDep =
+        new WorkflowDependency(WorkflowDependency.Type.CORE, CACHE_LOAD);
+    wc.jobsToRun.addJob(new CacheLoadTask(gadgetId, specCache), cacheLoadDep);
+
+    WorkflowDependency urlFetchDep =
+        new WorkflowDependency(WorkflowDependency.Type.CORE, URL_FETCH);
+    wc.jobsToRun.addJob(new SpecLoadTask(fetcher, gadgetId, specCache),
+                        urlFetchDep, cacheLoadDep);
+
+    WorkflowDependency enqueueFeatDep =
+        new WorkflowDependency(WorkflowDependency.Type.CORE, ENQUEUE_FEATURES);
+    wc.jobsToRun.addJob(new EnqueueFeaturesTask(), enqueueFeatDep, urlFetchDep);
+
+    // Instantiate CompletionService
+    CompletionService<GadgetException> processor =
+        new ExecutorCompletionService<GadgetException>(executor);
+
+    // All exceptions caught during processing
+    List<GadgetException> gadgetExceptions = new LinkedList<GadgetException>();
+
+    // Loop through queue of Callables, executing each in CompletionService
+    // whose precursors have been satisfied
+    int jobsSubmitted = 0;
+    do {
+      // Loop through all jobs, submitting to run if all deps satisfied
+      List<WorkflowJob> runThisCycle = new LinkedList<WorkflowJob>();
+      for (WorkflowJob candidate : wc.jobsToRun) {
+        if (candidate.ready(wc.depsDone)) {
+          runThisCycle.add(candidate);
+        }
+      }
+
+      // Fire off ready jobs and remove from jobsToRun list
+      for (WorkflowJob runJob : runThisCycle) {
+        processor.submit(runJob.task);
+        jobsSubmitted++;
+        wc.jobsToRun.remove(runJob);
+      }
+
+      // Wait around for at least one job to have completed.
+      // Completion of a job results in an additional dep added to wc.depsDone,
+      // thus potentially freeing up other jobs to run
+      Future<GadgetException> latestResult = null;
+      GadgetException gadgetException = null;
+      try {
+        latestResult = processor.take();
+      } catch (InterruptedException e) {
+        gadgetException = new GadgetException(
+            GadgetException.Code.INTERNAL_SERVER_ERROR, e);
+      }
+
+      // Ensure the task ran successfully
+      if (latestResult != null) {
+        try {
+          gadgetException = latestResult.get();
+        } catch (ExecutionException e) {
+          // TODO: convert into gadgetException with internal error type
+          gadgetException = new GadgetException(
+              GadgetException.Code.INTERNAL_SERVER_ERROR, e);
+        } catch (InterruptedException e) {
+          // TODO: convert into gadgetException with internal error type
+          gadgetException = new GadgetException(
+              GadgetException.Code.INTERNAL_SERVER_ERROR, e);
+        }
+      }
+
+      if (gadgetException != null) {
+        System.err.println(gadgetException.getCode().toString());
+        gadgetException.printStackTrace();
+        Throwable t = gadgetException.getCause();
+        if (t != null) {
+          t.printStackTrace();
+        }
+        // Add to list of all exceptions caught, clear jobs, and continue
+        // to aggressively catch as many exceptions as possible. Since
+        // tasks are running anyway, we may as well get their results in
+        // case it would be useful to the user.
+        gadgetExceptions.add(gadgetException);
+        wc.jobsToRun.clear();
+        jobsSubmitted = wc.depsDone.size();
+      }
+    } while(wc.jobsToRun.size() > 0 || jobsSubmitted > wc.depsDone.size());
+
+    if (gadgetExceptions.size() > 0) {
+      throw new GadgetProcessException(gadgetExceptions);
+    }
+
+    // terminate when all Callables are finished (or Exception detected?)
+    return wc.gadget;
+  }
+
+  public List<GadgetSpec.UserPref> getPrefsInfo(Gadget.ID gadgetId) {
+    return null;
+  }
+
+  private static final String CACHE_LOAD = "cache-load";
+  private static final String URL_FETCH = "url-fetch";
+  private static final String ENQUEUE_FEATURES = "enqueue-features";
+
+  private static class WorkflowJobList extends ArrayList<WorkflowJob> {
+    private final WorkflowContext wc;
+    public WorkflowJobList(WorkflowContext wc) {
+      this.wc = wc;
+    }
+
+    public void addJob(WorkflowTask task,
+                       WorkflowDependency done,
+                       WorkflowDependency... deps) {
+      task.setup(wc, done);
+      this.add(new WorkflowJob(task, deps));
+    }
+  }
+
+  private static class WorkflowDependency {
+    private static enum Type {
+      CORE, FEATURE_PREPARE, FEATURE_PROCESS
+    }
+
+    private final Type type;
+    private final String id;
+
+    private WorkflowDependency(Type type, String id) {
+      this.type = type;
+      this.id = id;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (other instanceof WorkflowDependency) {
+        WorkflowDependency wd = (WorkflowDependency)other;
+        return type.equals(wd.type) && id.equals(wd.id);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return (type.toString() + id).hashCode();
+    }
+  }
+
+  private static class WorkflowContext {
+    private Gadget gadget;
+    private GadgetContext context;
+    private WorkflowJobList jobsToRun;
+    private final Set<WorkflowDependency> depsDone;
+
+    private WorkflowContext(GadgetContext context) {
+      this.context = context;
+      this.depsDone = new HashSet<WorkflowDependency>();
+      this.jobsToRun = new WorkflowJobList(this);
+    }
+  }
+
+  private static class WorkflowJob {
+    private final WorkflowTask task;
+    private final List<WorkflowDependency> deps;
+
+    private WorkflowJob(WorkflowTask task, WorkflowDependency... deps) {
+      this.task = task;
+      this.deps = new LinkedList<WorkflowDependency>(Arrays.asList(deps));
+    }
+
+    private boolean ready(Set<WorkflowDependency> depsDone) {
+      for (WorkflowDependency dep : deps) {
+        if (!depsDone.contains(dep)) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+
+  private static class CacheLoadTask extends WorkflowTask {
+    private final GadgetView.ID gadgetId;
+    private final GadgetDataCache<GadgetSpec> specCache;
+
+    private CacheLoadTask(GadgetView.ID gadgetId,
+                          GadgetDataCache<GadgetSpec> specCache) {
+      this.gadgetId = gadgetId;
+      this.specCache = specCache;
+    }
+
+    @Override
+    public void run(WorkflowContext wc) throws GadgetException {
+      GadgetSpec spec = specCache.get(gadgetId.getKey());
+      if (spec != null) {
+        wc.gadget = new Gadget(gadgetId, spec);
+      }
+    }
+  }
+
+  private static class SpecLoadTask extends WorkflowTask {
+    private final RemoteContentFetcher fetcher;
+    private final GadgetView.ID gadgetId;
+    private final GadgetDataCache<GadgetSpec> specCache;
+
+    private SpecLoadTask(RemoteContentFetcher fetcher, GadgetView.ID gadgetId,
+                         GadgetDataCache<GadgetSpec> specCache) {
+      this.fetcher = fetcher;
+      this.gadgetId = gadgetId;
+      this.specCache = specCache;
+    }
+
+    @Override
+    public void run(WorkflowContext wc) throws GadgetException {
+      if (wc.gadget != null) {
+        // Already retrieved: do nothing.
+        return;
+      }
+
+      byte[] xml = fetcher.fetch(gadgetId.getURL()).getByteArray();
+      GadgetSpecParser specParser = new GadgetSpecParser();
+      GadgetSpec spec = specParser.parse(gadgetId, xml);
+      wc.gadget = new Gadget(gadgetId, spec);
+      // This isn't a separate job because if it is we'd just need another
+      // flag telling us not to store to the cache.
+      specCache.put(wc.gadget.getId().getKey(), wc.gadget.copy());
+    }
+  }
+
+  private static class EnqueueFeaturesTask extends WorkflowTask {
+    @Override
+    public void run(WorkflowContext wc) throws GadgetException {
+      List<String> needed = new LinkedList<String>();
+      Set<String> optionalNames = new HashSet<String>();
+      Map<String, GadgetSpec.FeatureSpec> requires = wc.gadget.getRequires();
+      for (Map.Entry<String, GadgetSpec.FeatureSpec> entry : requires.entrySet()) {
+        needed.add(entry.getKey());
+        if (entry.getValue().isOptional()) {
+          optionalNames.add(entry.getKey());
+        }
+      }
+
+      // Retrieve needed feature processors from registry
+      List<GadgetFeatureRegistry.Entry> resultsFound =
+          new LinkedList<GadgetFeatureRegistry.Entry>();
+      List<String> resultsMissing = new LinkedList<String>();
+      GadgetFeatureRegistry.getIncludedFeatures(needed,
+                                                resultsFound,
+                                                resultsMissing);
+
+      // Classify features this server is missing
+      List<String> missingRequired = new LinkedList<String>();
+      List<String> missingOptional = new LinkedList<String>();
+      for (String missingResult : resultsMissing) {
+        if (optionalNames.contains(missingResult)) {
+          missingOptional.add(missingResult);
+        } else {
+          missingRequired.add(missingResult);
+        }
+      }
+
+      if (missingRequired.size() > 0) {
+        // TODO: throw MissingFeaturesException, subclass of GadgetException,
+        // which is then processed at termination of the jobs loop
+        for (String missing : missingRequired) {
+          System.err.println("Unsupported: " + missing);
+        }
+        throw new GadgetException(GadgetException.Code.UNSUPPORTED_FEATURE);
+      }
+
+      if (missingOptional.size() > 0) {
+        // TODO: add custom task, dependent on nothing, adding metadata re:
+        // missing optionals to the gadget's output (satisfies HasFeature(...))
+      }
+
+      WorkflowDependency specLoadDep =
+          new WorkflowDependency(WorkflowDependency.Type.CORE, URL_FETCH);
+      for (GadgetFeatureRegistry.Entry entry : resultsFound) {
+        List<WorkflowDependency> prepareDeps =
+            new LinkedList<WorkflowDependency>();
+        List<WorkflowDependency> processDeps =
+          new LinkedList<WorkflowDependency>();
+
+        // sanity check: each depends on the spec having been loaded
+        prepareDeps.add(specLoadDep);
+
+        for (String featureDep : entry.getDependencies()) {
+          // prepare depends on all its own deps...
+          WorkflowDependency prepareNeedsDep =
+              new WorkflowDependency(WorkflowDependency.Type.FEATURE_PREPARE,
+                                     featureDep);
+          prepareDeps.add(prepareNeedsDep);
+
+          WorkflowDependency processNeedsDep =
+            new WorkflowDependency(WorkflowDependency.Type.FEATURE_PROCESS,
+                                   featureDep);
+          // Can't process until all dependencies prepare() and process()
+          // have completed.
+          processDeps.add(prepareNeedsDep);
+          processDeps.add(processNeedsDep);
+        }
+
+        // Create task for prepare and process, each with the dependency
+        // that running each satisfies
+        WorkflowDependency prepareDep =
+            new WorkflowDependency(WorkflowDependency.Type.FEATURE_PREPARE,
+                                   entry.getName());
+
+        // We must guarantee that process is called after prepare. This is
+        // implicitly stating that process has all of prepare's dependencies.
+        processDeps.add(prepareDep);
+        WorkflowDependency processDep =
+          new WorkflowDependency(WorkflowDependency.Type.FEATURE_PROCESS,
+                                 entry.getName());
+
+        // Then add a new job for each task, with appropriate execution
+        // precursors/dependencies, to the jobs queue
+        try {
+          GadgetFeature feature = entry.getFeature().newInstance();
+          wc.jobsToRun.addJob(new FeaturePrepareTask(entry.getName(), feature),
+                              prepareDep,
+                              prepareDeps.toArray(new WorkflowDependency[]{}));
+          wc.jobsToRun.addJob(new FeatureProcessTask(entry.getName(), feature),
+                              processDep,
+                              processDeps.toArray(new WorkflowDependency[]{}));
+        } catch (InstantiationException e) {
+          System.err.println("Unable to instantiate "
+                             + entry.getFeature().getName());
+        } catch (IllegalAccessException e) {
+          System.err.println("Unable to call no-arg constructor for "
+                             + entry.getFeature().getName());
+        }
+      }
+    }
+  }
+
+  private static class FeaturePrepareTask extends WorkflowTask {
+    private final GadgetFeature feature;
+    private final String name;
+    private FeaturePrepareTask(String name, GadgetFeature feature) {
+      this.name = name;
+      this.feature = feature;
+    }
+
+    @Override
+    public void run(WorkflowContext wc) throws GadgetException {
+      Map<String, String> params = Gadget.getFeatureParams(wc.gadget, name);
+      feature.prepare(wc.gadget, wc.context, params);
+    }
+  }
+
+  private static class FeatureProcessTask extends WorkflowTask {
+    private final GadgetFeature feature;
+    private final String name;
+    private FeatureProcessTask(String name, GadgetFeature feature) {
+      this.name = name;
+      this.feature = feature;
+    }
+
+    @Override
+    public void run(WorkflowContext wc) throws GadgetException {
+      Map<String, String> params = Gadget.getFeatureParams(wc.gadget, name);
+      feature.process(wc.gadget,  wc.context,  params);
+    }
+  }
+
+  private static abstract class WorkflowTask
+      implements Callable<GadgetException> {
+    // This class is mostly just an alias to Callable<GadgetException>
+    // providing a helper method for passing in context
+    public abstract void run(WorkflowContext wc) throws GadgetException;
+
+    private WorkflowContext wc;
+    private WorkflowDependency done;
+    public WorkflowTask setup(WorkflowContext wc, WorkflowDependency done) {
+      this.wc = wc;
+      this.done = done;
+      return this;
+    }
+
+    public GadgetException call() {
+      GadgetException ret = null;
+      try {
+        this.run(wc);
+      } catch (GadgetException e) {
+        ret = e;
+      } catch (Exception e) {
+        // TODO: capture Throwable cause in wrapped exception
+        ret = new GadgetException(
+            GadgetException.Code.INTERNAL_SERVER_ERROR, e);
+      } finally {
+        wc.depsDone.add(done);
+      }
+      return ret;
+    }
+  }
+
+  public static class GadgetProcessException extends Exception {
+    private final List<GadgetException> components;
+
+    public GadgetProcessException(List<GadgetException> components) {
+      this.components = components;
+    }
+
+    public GadgetProcessException(GadgetException.Code code) {
+      this.components = new ArrayList<GadgetException>();
+      this.components.add(new GadgetException(code));
+    }
+
+    public List<GadgetException> getComponents() {
+      return components;
+    }
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java?rev=603539&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java Wed Dec 12 02:33:35 2007
@@ -0,0 +1,87 @@
+/*
+ * Licensed 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 java.net.URL;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Represents a Gadget specification.
+ */
+public interface GadgetSpec {
+  public String getTitle();
+  public URL getTitleURL();
+  public String getDirectoryTitle();
+  public String getDescription();
+  public String getAuthor();
+  public String getAuthorEmail();
+  public String getScreenshot();
+  public String getThumbnail();
+
+  public static interface MessageBundle {
+    public Locale getLocale();
+    public URL getURL();
+    public boolean isRightToLeft();
+  }
+
+  public List<MessageBundle> getMessageBundles();
+
+  public static interface FeatureSpec {
+    public String getName();
+    public Map<String, String> getParams();
+    public boolean isOptional();
+  }
+
+  public Map<String, FeatureSpec> getRequires();
+  public List<String> getPreloads();
+
+  public static interface Icon {
+    public URL getURL();
+    public String getMode();
+    public String getType();
+  }
+
+  public List<Icon> getIcons();
+
+  public static interface UserPref {
+
+    public String getName();
+    public String getDisplayName();
+    public String getDefaultValue();
+    public boolean isRequired();
+
+    public static enum DataType {
+      STRING, HIDDEN, BOOL, ENUM, LIST, NUMBER
+    }
+
+    public DataType getDataType();
+  }
+
+  public List<UserPref> getUserPrefs();
+
+  public static enum ContentType {
+      HTML, URL
+  }
+
+  public ContentType getContentType();
+  public URL getContentHref();
+  public String getContentData();
+
+  /**
+   * @return A copy of the spec. This is NOT the same as clone().
+   */
+  public GadgetSpec copy();
+}
\ No newline at end of file



Re: svn commit: r603539 [1/3] - in /incubator/shindig/trunk: ./ java/ java/gadgets/ java/gadgets/src/ java/gadgets/src/main/ java/gadgets/src/main/java/ java/gadgets/src/main/java/org/ java/gadgets/src/main/java/org/apache/ java/gadgets/src/main/java/org/a...

Posted by Brian McCallister <br...@skife.org>.
Just skimmed the initial checkin. Looks really nice! A couple points:

javascript/cotainer/gadgets.js
	* Header reads:
	  // Copyright 2007 Google Inc.
	  // All Rights Reserved.

javascript/container/cookies.js
	* Header reads:
	  // Copyright 2006 Google Inc.
	  // All Rights Reserved.

javascript/container/ifpc_relay.html relies on http://www.google.com/ig/ifpc.js
	* can we remove the dependency on being on the internet for local dev,
	  and on Google's infrastructure in particular?
	* What are the license terms for that chunk of javascript?

-Brian

On Dec 12, 2007, at 2:33 AM, johnh@apache.org wrote:

> Author: johnh
> Date: Wed Dec 12 02:33:35 2007
> New Revision: 603539
>
> URL: http://svn.apache.org/viewvc?rev=603539&view=rev
> Log:
> Initial commit of Shindig Gadget Container JavaScript and Gadget  
> Server.
>
> Includes:
>
>  * Gadget Container JavaScript. Code embedded into an arbitrary web  
> page
>    enabling it to render and manage Gadgets.
>    + Support for rendering using gmodules.com or a Shindig Gadget  
> Server
>    + Basic IFPC (gadget <-> container communication) support via  
> inclusion
>      of Google IFPC library
>    + Simple layout management: render in DIV (StaticLayoutManager);
>      render multiple Gadgets left-aligned in DIV  
> (FloatLeftLayoutManager)
>    + UserPrefs support through JS interface
>    + Cookie-based UserPrefs implementation
>    + _IG_SetTitle container-side support
>    + _IG_AdjustIframeHeight container-side support
>    + Sample pages demonstrating all the above functionality
>
>  * Gadget Server (written in Java). Web server that processes Gadget
>    requests, parsing their spec XML, processing it through a workflow
>    of core and extension (<Require>/<Optional>) Features, and  
> serializing
>    output for rendering the Gadget to the end user.
>    + Core server implementation constructing processing workflows  
> out of
>      GadgetFeature objects providing various feature support
>    + GadgetFeature interface providing core extensibility mechanism  
> for
>      the Gadgets platform; extenders implement GadgetFeature and  
> register
>      their implementation with GadgetFeatureRegistry
>    + Gadget spec XML parser
>    + Simple caching API with in-memory Map-based implementations as  
> samples
>    + Remote content (eg. HTTP) fetcher interface
>    + Basic java.net-based RemoteContentFetcher implementation
>    + "Hangman" variable substitution support (eg. __MSG_foo__),  
> including BIDI,
>      MSG (message bundles) and UP (user prefs) type variables
>    + Early JavaScript-based Feature support through JsLibrary* classes
>    + Content proxy including JSON support
>
>  * A handful of tests for various Server components.
>
>  * Maven build support.
>
> This submission represents significant contributions by:
>  fargo@google.com, etnu@google.com, dharkness@google.com,
>  jyang@google.com, wangz@google.com, dcoker@google.com
>
> Much more to come!
>
>
> Added:
>    incubator/shindig/trunk/COPYING
>    incubator/shindig/trunk/NOTICE
>    incubator/shindig/trunk/README
>    incubator/shindig/trunk/java/
>    incubator/shindig/trunk/java/gadgets/
>    incubator/shindig/trunk/java/gadgets/README
>    incubator/shindig/trunk/java/gadgets/pom.xml
>    incubator/shindig/trunk/java/gadgets/src/
>    incubator/shindig/trunk/java/gadgets/src/main/
>    incubator/shindig/trunk/java/gadgets/src/main/java/
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicGadgetDataCache.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicRemoteContentFetcher.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BidiSubstituter.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/CoreJsFeature.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/Gadget.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetContext.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetDataCache.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetException.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeature.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeatureRegistry.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/GadgetSpec.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetSpecParser.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetView.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/JsLibrary.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/JsLibraryFeature.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/MessageBundle.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/MessageBundleParser.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/MessageBundleSubstituter.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/ModuleSubstituter.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/RemoteContent.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/RemoteContentFetcher.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/SpecParserException.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/Substitutions.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/UserPrefSubstituter.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/UserPrefs.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/Utf8InputStream.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/http/
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/http/AnalyticsFeature.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/http/BasicGadgetHttpServlet.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/http/BasicHttpContext.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/http/JsonpProxyServlet.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/http/ProxyHandler.java
>    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/http/ProxyServlet.java
>    incubator/shindig/trunk/java/gadgets/src/main/webapp/
>    incubator/shindig/trunk/java/gadgets/src/main/webapp/WEB-INF/
>    incubator/shindig/trunk/java/gadgets/src/main/webapp/WEB-INF/ 
> web.xml
>    incubator/shindig/trunk/java/gadgets/src/test/
>    incubator/shindig/trunk/java/gadgets/src/test/java/
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/ 
> shindig/
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/ 
> shindig/gadgets/
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/ 
> shindig/gadgets/EasyMockTestCase.java
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/ 
> shindig/gadgets/GadgetServerTest.java
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/ 
> shindig/gadgets/GadgetSpecTestFixture.java
>    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/ 
> shindig/gadgets/SubstitutionsTest.java
>    incubator/shindig/trunk/javascript/
>    incubator/shindig/trunk/javascript/README
>    incubator/shindig/trunk/javascript/container/
>    incubator/shindig/trunk/javascript/container/cookies.js
>    incubator/shindig/trunk/javascript/container/gadgets.css
>    incubator/shindig/trunk/javascript/container/gadgets.js
>    incubator/shindig/trunk/javascript/container/ifpc_relay.html
>    incubator/shindig/trunk/javascript/container/json.js
>    incubator/shindig/trunk/javascript/container/sample1.html
>    incubator/shindig/trunk/javascript/container/sample2.html
>    incubator/shindig/trunk/javascript/container/sample3.html
>    incubator/shindig/trunk/javascript/container/sample4.html
>
> Added: incubator/shindig/trunk/COPYING
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/COPYING?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/COPYING (added)
> +++ incubator/shindig/trunk/COPYING Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,201 @@
> +                                 Apache License
> +                           Version 2.0, January 2004
> +                        http://www.apache.org/licenses/
> +
> +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
> +
> +   1. Definitions.
> +
> +      "License" shall mean the terms and conditions for use,  
> reproduction,
> +      and distribution as defined by Sections 1 through 9 of this  
> document.
> +
> +      "Licensor" shall mean the copyright owner or entity  
> authorized by
> +      the copyright owner that is granting the License.
> +
> +      "Legal Entity" shall mean the union of the acting entity and  
> all
> +      other entities that control, are controlled by, or are under  
> common
> +      control with that entity. For the purposes of this definition,
> +      "control" means (i) the power, direct or indirect, to cause the
> +      direction or management of such entity, whether by contract or
> +      otherwise, or (ii) ownership of fifty percent (50%) or more  
> of the
> +      outstanding shares, or (iii) beneficial ownership of such  
> entity.
> +
> +      "You" (or "Your") shall mean an individual or Legal Entity
> +      exercising permissions granted by this License.
> +
> +      "Source" form shall mean the preferred form for making  
> modifications,
> +      including but not limited to software source code,  
> documentation
> +      source, and configuration files.
> +
> +      "Object" form shall mean any form resulting from mechanical
> +      transformation or translation of a Source form, including but
> +      not limited to compiled object code, generated documentation,
> +      and conversions to other media types.
> +
> +      "Work" shall mean the work of authorship, whether in Source or
> +      Object form, made available under the License, as indicated  
> by a
> +      copyright notice that is included in or attached to the work
> +      (an example is provided in the Appendix below).
> +
> +      "Derivative Works" shall mean any work, whether in Source or  
> Object
> +      form, that is based on (or derived from) the Work and for  
> which the
> +      editorial revisions, annotations, elaborations, or other  
> modifications
> +      represent, as a whole, an original work of authorship. For  
> the purposes
> +      of this License, Derivative Works shall not include works  
> that remain
> +      separable from, or merely link (or bind by name) to the  
> interfaces of,
> +      the Work and Derivative Works thereof.
> +
> +      "Contribution" shall mean any work of authorship, including
> +      the original version of the Work and any modifications or  
> additions
> +      to that Work or Derivative Works thereof, that is intentionally
> +      submitted to Licensor for inclusion in the Work by the  
> copyright owner
> +      or by an individual or Legal Entity authorized to submit on  
> behalf of
> +      the copyright owner. For the purposes of this definition,  
> "submitted"
> +      means any form of electronic, verbal, or written  
> communication sent
> +      to the Licensor or its representatives, including but not  
> limited to
> +      communication on electronic mailing lists, source code  
> control systems,
> +      and issue tracking systems that are managed by, or on behalf  
> of, the
> +      Licensor for the purpose of discussing and improving the  
> Work, but
> +      excluding communication that is conspicuously marked or  
> otherwise
> +      designated in writing by the copyright owner as "Not a  
> Contribution."
> +
> +      "Contributor" shall mean Licensor and any individual or Legal  
> Entity
> +      on behalf of whom a Contribution has been received by  
> Licensor and
> +      subsequently incorporated within the Work.
> +
> +   2. Grant of Copyright License. Subject to the terms and  
> conditions of
> +      this License, each Contributor hereby grants to You a  
> perpetual,
> +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
> +      copyright license to reproduce, prepare Derivative Works of,
> +      publicly display, publicly perform, sublicense, and  
> distribute the
> +      Work and such Derivative Works in Source or Object form.
> +
> +   3. Grant of Patent License. Subject to the terms and conditions of
> +      this License, each Contributor hereby grants to You a  
> perpetual,
> +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
> +      (except as stated in this section) patent license to make,  
> have made,
> +      use, offer to sell, sell, import, and otherwise transfer the  
> Work,
> +      where such license applies only to those patent claims  
> licensable
> +      by such Contributor that are necessarily infringed by their
> +      Contribution(s) alone or by combination of their  
> Contribution(s)
> +      with the Work to which such Contribution(s) was submitted. If  
> You
> +      institute patent litigation against any entity (including a
> +      cross-claim or counterclaim in a lawsuit) alleging that the  
> Work
> +      or a Contribution incorporated within the Work constitutes  
> direct
> +      or contributory patent infringement, then any patent licenses
> +      granted to You under this License for that Work shall terminate
> +      as of the date such litigation is filed.
> +
> +   4. Redistribution. You may reproduce and distribute copies of the
> +      Work or Derivative Works thereof in any medium, with or without
> +      modifications, and in Source or Object form, provided that You
> +      meet the following conditions:
> +
> +      (a) You must give any other recipients of the Work or
> +          Derivative Works a copy of this License; and
> +
> +      (b) You must cause any modified files to carry prominent  
> notices
> +          stating that You changed the files; and
> +
> +      (c) You must retain, in the Source form of any Derivative Works
> +          that You distribute, all copyright, patent, trademark, and
> +          attribution notices from the Source form of the Work,
> +          excluding those notices that do not pertain to any part of
> +          the Derivative Works; and
> +
> +      (d) If the Work includes a "NOTICE" text file as part of its
> +          distribution, then any Derivative Works that You  
> distribute must
> +          include a readable copy of the attribution notices  
> contained
> +          within such NOTICE file, excluding those notices that do  
> not
> +          pertain to any part of the Derivative Works, in at least  
> one
> +          of the following places: within a NOTICE text file  
> distributed
> +          as part of the Derivative Works; within the Source form or
> +          documentation, if provided along with the Derivative  
> Works; or,
> +          within a display generated by the Derivative Works, if and
> +          wherever such third-party notices normally appear. The  
> contents
> +          of the NOTICE file are for informational purposes only and
> +          do not modify the License. You may add Your own attribution
> +          notices within Derivative Works that You distribute,  
> alongside
> +          or as an addendum to the NOTICE text from the Work,  
> provided
> +          that such additional attribution notices cannot be  
> construed
> +          as modifying the License.
> +
> +      You may add Your own copyright statement to Your  
> modifications and
> +      may provide additional or different license terms and  
> conditions
> +      for use, reproduction, or distribution of Your modifications,  
> or
> +      for any such Derivative Works as a whole, provided Your use,
> +      reproduction, and distribution of the Work otherwise complies  
> with
> +      the conditions stated in this License.
> +
> +   5. Submission of Contributions. Unless You explicitly state  
> otherwise,
> +      any Contribution intentionally submitted for inclusion in the  
> Work
> +      by You to the Licensor shall be under the terms and  
> conditions of
> +      this License, without any additional terms or conditions.
> +      Notwithstanding the above, nothing herein shall supersede or  
> modify
> +      the terms of any separate license agreement you may have  
> executed
> +      with Licensor regarding such Contributions.
> +
> +   6. Trademarks. This License does not grant permission to use the  
> trade
> +      names, trademarks, service marks, or product names of the  
> Licensor,
> +      except as required for reasonable and customary use in  
> describing the
> +      origin of the Work and reproducing the content of the NOTICE  
> file.
> +
> +   7. Disclaimer of Warranty. Unless required by applicable law or
> +      agreed to in writing, Licensor provides the Work (and each
> +      Contributor provides its Contributions) on an "AS IS" BASIS,
> +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> +      implied, including, without limitation, any warranties or  
> conditions
> +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
> +      PARTICULAR PURPOSE. You are solely responsible for  
> determining the
> +      appropriateness of using or redistributing the Work and  
> assume any
> +      risks associated with Your exercise of permissions under this  
> License.
> +
> +   8. Limitation of Liability. In no event and under no legal theory,
> +      whether in tort (including negligence), contract, or otherwise,
> +      unless required by applicable law (such as deliberate and  
> grossly
> +      negligent acts) or agreed to in writing, shall any  
> Contributor be
> +      liable to You for damages, including any direct, indirect,  
> special,
> +      incidental, or consequential damages of any character arising  
> as a
> +      result of this License or out of the use or inability to use  
> the
> +      Work (including but not limited to damages for loss of  
> goodwill,
> +      work stoppage, computer failure or malfunction, or any and all
> +      other commercial damages or losses), even if such Contributor
> +      has been advised of the possibility of such damages.
> +
> +   9. Accepting Warranty or Additional Liability. While  
> redistributing
> +      the Work or Derivative Works thereof, You may choose to offer,
> +      and charge a fee for, acceptance of support, warranty,  
> indemnity,
> +      or other liability obligations and/or rights consistent with  
> this
> +      License. However, in accepting such obligations, You may act  
> only
> +      on Your own behalf and on Your sole responsibility, not on  
> behalf
> +      of any other Contributor, and only if You agree to indemnify,
> +      defend, and hold each Contributor harmless for any liability
> +      incurred by, or claims asserted against, such Contributor by  
> reason
> +      of your accepting any such warranty or additional liability.
> +
> +   END OF TERMS AND CONDITIONS
> +
> +   APPENDIX: How to apply the Apache License to your work.
> +
> +      To apply the Apache License to your work, attach the following
> +      boilerplate notice, with the fields enclosed by brackets "[]"
> +      replaced with your own identifying information. (Don't include
> +      the brackets!)  The text should be enclosed in the appropriate
> +      comment syntax for the file format. We also recommend that a
> +      file or class name and description of purpose be included on  
> the
> +      same "printed page" as the copyright notice for easier
> +      identification within third-party archives.
> +
> +   Copyright 2007 Google Inc.
> +
> +   Licensed 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.
>
> Added: incubator/shindig/trunk/NOTICE
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/NOTICE?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/NOTICE (added)
> +++ incubator/shindig/trunk/NOTICE Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,2 @@
> +This product includes software (Gadget Server, Gadget Container)  
> developed by
> +Google Inc. (http://code.google.com/)
>
> Added: incubator/shindig/trunk/README
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/README?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/README (added)
> +++ incubator/shindig/trunk/README Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,10 @@
> +Welcome to Apache Shindig!
> +
> +* Read java/gadgets/README for instructions on how to start up your  
> own
> +  Shindig Gadget Server.
> +
> +* Read javascript/README for instructions for using the Shindig  
> Gadget
> +  Container JavaScript to enable your page to render Gadgets using
> +  gmodules.com or a server started up as described above.
> +
> +For more information, see http://incubator.apache.org/projects/shindig.html
>
> Added: incubator/shindig/trunk/java/gadgets/README
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/README?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/README (added)
> +++ incubator/shindig/trunk/java/gadgets/README Wed Dec 12 02:33:35  
> 2007
> @@ -0,0 +1,16 @@
> +Installing and Running Shindig Gadget Server
> +============================================
> +
> +1) Install Maven 2.0 (see http://maven.apache.org)
> +
> +2) Make sure the JAVA_HOME environment variable is set to the  
> location of your
> +   JDK/JRE, and that the maven executable is in your PATH.
> +
> +3) From java/gadgets/...
> +   * mvn package - Builds Gadget Server and runs tests.
> +   * mvn jetty:run - Builds Gadget Server (no tests), installs  
> Servlet at localhost:8080.
> +
> +4) Hit server at http://localhost:8080/gadgets/ifr?url=<gadget-url>
> +   Example: http://localhost:8080/gadgets/ifr?url=http://www.labpixies.com/campaigns/todo/todo.xml
> +
> +For more information, see http://incubator.apache.org/projects/shindig.html
>
> Added: incubator/shindig/trunk/java/gadgets/pom.xml
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/pom.xml?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/pom.xml (added)
> +++ incubator/shindig/trunk/java/gadgets/pom.xml Wed Dec 12 02:33:35  
> 2007
> @@ -0,0 +1,92 @@
> +<?xml version="1.0"?>
> +<!--
> +
> +  Licensed 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.
> +
> +-->
> +<project xmlns="http://maven.apache.org/POM/4.0.0"
> +    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
> +    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd 
> ">
> +
> +  <modelVersion>4.0.0</modelVersion>
> +  <groupId>org.apache.shindig</groupId>
> +  <artifactId>gadgets</artifactId>
> +  <version>0.5</version>
> +  <packaging>war</packaging>
> +
> +  <name>Shindig Gadget Server</name>
> +  <url>http://shindig.apache.org</url>
> +
> +  <licenses>
> +    <license>
> +      <name>Apache 2</name>
> +      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
> +    </license>
> +  </licenses>
> +
> +  <dependencies>
> +    <dependency>
> +      <groupId>org.json</groupId>
> +      <artifactId>json</artifactId>
> +      <version>20070829</version>
> +      <scope>compile</scope>
> +    </dependency>
> +    <dependency>
> +      <groupId>javax.servlet</groupId>
> +      <artifactId>servlet-api</artifactId>
> +      <version>2.3</version>
> +      <scope>compile</scope>
> +    </dependency>
> +
> +    <dependency>
> +      <groupId>junit</groupId>
> +      <artifactId>junit</artifactId>
> +      <version>3.8.1</version>
> +      <scope>test</scope>
> +    </dependency>
> +    <dependency>
> +      <groupId>org.easymock</groupId>
> +      <artifactId>easymock</artifactId>
> +      <version>2.3</version>
> +      <scope>test</scope>
> +    </dependency>
> +    <dependency>
> +      <groupId>org.easymock</groupId>
> +      <artifactId>easymockclassextension</artifactId>
> +      <version>2.2.2</version>
> +      <scope>test</scope>
> +    </dependency>
> +  </dependencies>
> +
> +  <build>
> +    <finalName>gadgets</finalName>
> +    <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
> +    <testSourceDirectory>${basedir}/src/test/java</ 
> testSourceDirectory>
> +    <outputDirectory>${basedir}/target/classes</outputDirectory>
> +    <testOutputDirectory>${basedir}/target/test-classes</ 
> testOutputDirectory>
> +    <plugins>
> +      <plugin>
> +        <groupId>org.apache.maven.plugins</groupId>
> +        <artifactId>maven-compiler-plugin</artifactId>
> +          <configuration>
> +            <source>1.5</source>
> +            <target>1.5</target>
> +         </configuration>
> +      </plugin>
> +      <plugin>
> +        <groupId>org.mortbay.jetty</groupId>
> +        <artifactId>maven-jetty-plugin</artifactId>
> +      </plugin>
> +    </plugins>
> +  </build>
> +</project>
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicGadgetDataCache.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetDataCache.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicGadgetDataCache.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicGadgetDataCache.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,39 @@
> +/*
> + * Licensed 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.GadgetDataCache;
> +
> +import java.util.HashMap;
> +import java.util.Map;
> +
> +/**
> + * Basic implementation of an in-memory data cache, backed by a
> + * {@code HashMap}.
> + *
> + * @param <T> Type of data to store in the cache.
> + */
> +public class BasicGadgetDataCache<T> implements GadgetDataCache<T> {
> +  private Map<String, T> cache = new HashMap<String, T>();
> +
> +  /** {@inheritDoc} */
> +  public T get(String key) {
> +    return cache.get(key);
> +  }
> +
> +  /** {@inheritDoc} */
> +  public void put(String key, T value) {
> +    cache.put(key, value);
> +  }
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicRemoteContentFetcher.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicRemoteContentFetcher.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicRemoteContentFetcher.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BasicRemoteContentFetcher.java Wed Dec 12 02:33:35  
> 2007
> @@ -0,0 +1,73 @@
> +/*
> + * Licensed 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.RemoteContentFetcher;
> +import org.apache.shindig.gadgets.RemoteContent;
> +
> +import java.io.ByteArrayOutputStream;
> +import java.io.IOException;
> +import java.io.InputStream;
> +import java.net.HttpURLConnection;
> +import java.net.URL;
> +import java.util.List;
> +import java.util.Map;
> +
> +/**
> + * Implementation of a {@code RemoteObjectFetcher} using standard  
> java.net
> + * classes. Only supports HTTP fetching at present.
> + */
> +public class BasicRemoteContentFetcher implements  
> RemoteContentFetcher {
> +  private static final int CONNECT_TIMEOUT_MS = 5000;
> +
> +  private final int maxObjSize;
> +
> +  /**
> +   * Creates a new fetcher capable of retrieving objects {@code  
> maxObjSize}
> +   * bytes or smaller in size.
> +   * @param maxObjSize Maximum size, in bytes, of object to fetch
> +   */
> +  public BasicRemoteContentFetcher(int maxObjSize) {
> +    this.maxObjSize = maxObjSize;
> +  }
> +
> +  /** {@inheritDoc} */
> +  public RemoteContent fetch(URL url) {
> +    ByteArrayOutputStream out = new ByteArrayOutputStream();
> +
> +    int responseCode;
> +    HttpURLConnection fetcher;
> +    Map<String, List<String>> headers = null;
> +
> +    try {
> +      fetcher = (HttpURLConnection) url.openConnection();
> +      fetcher.setInstanceFollowRedirects(true);
> +      fetcher.setConnectTimeout(CONNECT_TIMEOUT_MS);
> +
> +      responseCode = fetcher.getResponseCode();
> +      headers = fetcher.getHeaderFields();
> +
> +      byte chunk[] = new byte[8192];
> +      int chunkSize;
> +      InputStream in = fetcher.getInputStream();
> +      while (out.size() < maxObjSize && (chunkSize =  
> in.read(chunk)) != -1) {
> +        out.write(chunk, 0, chunkSize);
> +      }
> +    } catch (IOException e) {
> +      responseCode = 500;
> +    }
> +
> +    return new RemoteContent(responseCode, out.toByteArray(),  
> headers);
> +  }
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BidiSubstituter.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BidiSubstituter.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BidiSubstituter.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/BidiSubstituter.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,86 @@
> +/*
> + * Licensed 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 java.util.List;
> +import java.util.Locale;
> +import java.util.Map;
> +
> +/**
> + * Provides static hangman substitutions for bidirectional language  
> support.
> + * Useful for generating internationalized layouts using CSS.
> + */
> +public class BidiSubstituter implements GadgetFeature {
> +
> +  /**
> +   * Fetches a message bundle spec from the {@code GadgetSpec} for  
> the
> +   * provided locale.
> +   * @param spec Gadget spec from which to retrieve message bundle  
> spec
> +   * @param locale Locale of message bundle to retrieve
> +   * @return The message bundle, or null if not found
> +   */
> +  private GadgetSpec.MessageBundle getBundle(GadgetSpec spec,
> +                                             Locale locale) {
> +    List<GadgetSpec.MessageBundle> bundles =  
> spec.getMessageBundles();
> +    for (GadgetSpec.MessageBundle bundle : bundles) {
> +      if (bundle.getLocale().equals(locale)) {
> +        return bundle;
> +      }
> +    }
> +    return null;
> +  }
> +
> +  /** {@inheritDoc} */
> +  public void prepare(GadgetView spec,
> +                      GadgetContext context,
> +                      Map<String, String> params) {
> +    // Nothing here.
> +  }
> +
> +  /**
> +   * Populates bidi substitutions.
> +   * @param gadget Gadget object to process
> +   * @param context Context in which Gadget is being processed
> +   */
> +  public void process(Gadget gadget,
> +                      GadgetContext context,
> +                      Map<String, String> params) {
> +    Substitutions subst = gadget.getSubstitutions();
> +    Locale locale = context.getLocale();
> +    // Find an appropriate bundle for the ltr flag.
> +    GadgetSpec.MessageBundle bundle = getBundle(gadget, locale);
> +    if (null == bundle) {
> +      bundle = getBundle(gadget, new Locale(locale.getLanguage(),  
> "all"));
> +    }
> +    if (null == bundle) {
> +      bundle = getBundle(gadget, new Locale("all", "all"));
> +    }
> +    boolean rtl = false;
> +    if (bundle != null) {
> +      rtl = bundle.isRightToLeft();
> +    }
> +    subst.addSubstitution(Substitutions.Type.BIDI,
> +                          "START_EDGE",
> +                          rtl ? "right" : "left");
> +    subst.addSubstitution(Substitutions.Type.BIDI,
> +                          "END_EDGE",
> +                          rtl ? "left" : "right");
> +    subst.addSubstitution(Substitutions.Type.BIDI,
> +                          "DIR",
> +                          rtl ? "rtl" : "ltr");
> +    subst.addSubstitution(Substitutions.Type.BIDI,
> +                          "REVERSE_DIR",
> +                          rtl ? "ltr" : "rtl");
> +  }
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/CoreJsFeature.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/CoreJsFeature.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/CoreJsFeature.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/CoreJsFeature.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,27 @@
> +/*
> + * Licensed 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 java.util.Map;
> +
> +/**
> + * Adds all core javascript dependencies that are needed by all  
> gadgets.
> + */
> +public class CoreJsFeature extends JsLibraryFeature {
> +  @Override
> +  public void process(Gadget gadget, GadgetContext context,
> +      Map<String, String> params) {
> +    gadget.addJsLibrary(JsLibrary.file("http://gmodules.com/ig/extern_js/f/CgJlbhICdXMrMAE4ACw/6gZqwg2JpuM.js 
> "));
> +  }
> +}
> \ No newline at end of file
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/Gadget.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/Gadget.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/Gadget.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/Gadget.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,296 @@
> +/*
> + * Licensed 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 java.net.MalformedURLException;
> +import java.net.URL;
> +import java.util.ArrayList;
> +import java.util.Collections;
> +import java.util.HashMap;
> +import java.util.LinkedList;
> +import java.util.List;
> +import java.util.Map;
> +
> +/**
> + * Intermediary representation of all state associated with  
> processing
> + * of a single gadget request.
> + *
> + * This class is constructed by an immutable base {@code GadgetSpec},
> + * and is modified in parallel by a number of {@code GadgetFeature}
> + * processors, in an order defined by their dependencies, in
> + * {@code GadgetServer}.
> + *
> + * Upon completion of processing, a {@code Gadget} is serialized as  
> appropriate
> + * to whatever output format is appropriate (eg. as gadget content  
> in an
> + * IFRAME), potentially with post-processing such as HTML whitespace
> + * compression or HTML+JS (Caja) rewriting applied.
> + *
> + * "Hangman" variable substitutions (eg. __MSG_foo__) are performed  
> as needed
> + * and transparently for fields that support this functionality.
> + */
> +public class Gadget implements GadgetView {
> +  private final ID id;
> +  private final GadgetSpec baseSpec;
> +  private final Substitutions substitutions;
> +  private final Map<String, String> userPrefValues;
> +  private final List<JsLibrary> jsLibraries;
> +
> +  public static class GadgetId implements GadgetView.ID {
> +    private final URL url;
> +    private final int moduleId;
> +
> +    public GadgetId(URL url, int moduleId) {
> +      this.url = url;
> +      this.moduleId = moduleId;
> +    }
> +
> +    @Override
> +    public boolean equals(Object comp) {
> +      if (comp instanceof GadgetView.ID) {
> +        GadgetView.ID id = (GadgetView.ID)comp;
> +        return id.getURL() == url &&
> +               id.getModuleId() == moduleId;
> +      } else {
> +        return false;
> +      }
> +    }
> +
> +    @Override
> +    public int hashCode() {
> +      int result = 17;
> +      result = (37 * result) + url.hashCode();
> +      result = (37 * result) + moduleId;
> +      return result;
> +    }
> +
> +    public URL getURL() {
> +      return url;
> +    }
> +
> +    public int getModuleId() {
> +      return moduleId;
> +    }
> +
> +    public String getKey() {
> +      return url.toString();
> +    }
> +  }
> +
> +  /**
> +   * Create a new {@code Gadget} devoid of processing modifications.
> +   * @param id Identifier used to retrieve {@code baseSpec}
> +   * @param baseSpec Base (immutable) {@code GadgetSpec} on which  
> this is based
> +   */
> +  public Gadget(ID id, GadgetSpec baseSpec) {
> +    this.id = id;
> +    this.baseSpec = baseSpec;
> +    substitutions = new Substitutions();
> +    userPrefValues = new HashMap<String, String>();
> +    jsLibraries = new LinkedList<JsLibrary>();
> +  }
> +
> +  /**
> +   * @return Global identifier used to retrieve gadget's spec
> +   */
> +  public ID getId() {
> +    return id;
> +  }
> +
> +  /**
> +   * @return GadgetSpec that backs this Gadget. Package scope for  
> tests.
> +   */
> +  GadgetSpec getBaseSpec() {
> +    return baseSpec;
> +  }
> +
> +  /**
> +   * @return Object containing all hangman substitutions applied to  
> this gadget
> +   */
> +  public Substitutions getSubstitutions() {
> +    return substitutions;
> +  }
> +
> +  // GadgetSpec accessors
> +
> +  /**
> +   * @return Gadget title with substitutions applied
> +   */
> +  public String getTitle() {
> +    return substitutions.substitute(baseSpec.getTitle());
> +  }
> +
> +  /**
> +   * @return URL used as a target for Gadget's title link, or null  
> if malformed
> +   */
> +  public URL getTitleURL() {
> +    URL ret = null;
> +    String urlStr = baseSpec.getTitleURL().toString();
> +    try {
> +      ret = new URL(substitutions.substitute(urlStr));
> +    } catch (MalformedURLException e) {
> +      return null;
> +    }
> +    return ret;
> +  }
> +
> +  /**
> +   * @return String used to describe this Gadget in directories, with
> +   * substitutions applied
> +   */
> +  public String getDirectoryTitle() {
> +    return substitutions.substitute(baseSpec.getDirectoryTitle());
> +  }
> +
> +  /**
> +   * @return Extended description of {@code Gadget}, with  
> substitutions applied
> +   */
> +  public String getDescription() {
> +    return substitutions.substitute(baseSpec.getDescription());
> +  }
> +
> +  /**
> +   * @return Name of this Gadget's author as specified in its spec
> +   */
> +  public String getAuthor() {
> +    return baseSpec.getAuthor();
> +  }
> +
> +  /**
> +   * @return E-mail address of this Gadget's author as specified in  
> its spec
> +   */
> +  public String getAuthorEmail() {
> +    return baseSpec.getAuthorEmail();
> +  }
> +
> +  // TODO: make this URL?
> +  public String getScreenshot() {
> +    return baseSpec.getScreenshot();
> +  }
> +
> +  // TODO: make this URL?
> +  public String getThumbnail() {
> +    return baseSpec.getThumbnail();
> +  }
> +
> +  public List<MessageBundle> getMessageBundles() {
> +    return new  
> ArrayList<MessageBundle>(baseSpec.getMessageBundles());
> +  }
> +
> +  /**
> +   * @return List of all {@code FeatureSpec}s declared by this gadget
> +   */
> +  public Map<String, FeatureSpec> getRequires() {
> +    return Collections.unmodifiableMap(baseSpec.getRequires());
> +  }
> +
> +  /**
> +   * @return All JS libraries needed to render this gadget.
> +   */
> +  public List<JsLibrary> getJsLibraries() {
> +    return Collections.unmodifiableList(jsLibraries);
> +  }
> +
> +  /**
> +   * @param library
> +   */
> +  public void addJsLibrary(JsLibrary library) {
> +    jsLibraries.add(library);
> +  }
> +
> +  /**
> +   * Extracts parameters for the given feature.
> +   *
> +   * @param gadget
> +   * @param feature
> +   * @return The parameters, or an empty map.
> +   */
> +  @SuppressWarnings("unchecked")
> +  public static Map<String, String> getFeatureParams(Gadget gadget,
> +                                                     String  
> feature) {
> +    GadgetSpec.FeatureSpec spec = gadget.getRequires().get(feature);
> +    if (spec == null) {
> +      return Collections.EMPTY_MAP;
> +    } else {
> +      return spec.getParams();
> +    }
> +  }
> +
> +  /**
> +   * @return List of all preload URLs declared, with substitutions  
> applied
> +   */
> +  public List<String> getPreloads() {
> +    List<String> ret = new LinkedList<String>();
> +    for (String preload : baseSpec.getPreloads()) {
> +      ret.add(substitutions.substitute(preload));
> +    }
> +    return ret;
> +  }
> +
> +  /**
> +   * @return List of icons defined in gadget spec
> +   */
> +  public List<Icon> getIcons() {
> +    return Collections.unmodifiableList(baseSpec.getIcons());
> +  }
> +
> +  /**
> +   * @return List of all user pref specs defined in gadget spec
> +   */
> +  public List<UserPref> getUserPrefs() {
> +    return Collections.unmodifiableList(baseSpec.getUserPrefs());
> +  }
> +
> +  public Map<String, String> getUserPrefValues() {
> +    return Collections.unmodifiableMap(userPrefValues);
> +  }
> +
> +  /**
> +   * @return Type of gadget to render
> +   */
> +  public ContentType getContentType() {
> +    return baseSpec.getContentType();
> +  }
> +
> +  /**
> +   * @return URL of gadget to render of type == URL; null if  
> malformed/missing
> +   */
> +  public URL getContentHref() {
> +    if (getContentType() != ContentType.URL) {
> +      return null;
> +    }
> +
> +    URL ret = null;
> +    String urlStr = baseSpec.getContentHref().toString();
> +    try {
> +      ret = new URL(substitutions.substitute(urlStr));
> +    } catch (MalformedURLException e) {
> +      return null;
> +    }
> +    return ret;
> +  }
> +
> +  /**
> +   * @return Gadget contents with all substitutions applied
> +   */
> +  public String getContentData() {
> +    return substitutions.substitute(baseSpec.getContentData());
> +  }
> +
> +  /**
> +   * @return Copy of base spec that created this {@code Gadget}
> +   */
> +  public GadgetSpec copy() {
> +    return baseSpec.copy();
> +  }
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetContext.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetContext.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetContext.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetContext.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,51 @@
> +/*
> + * Licensed 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 java.util.Locale;
> +
> +/**
> + * Bundles together per-server data and helper mechanisms providing
> + * generic functionality such as retrieval of remote data and  
> caching.
> + */
> +public class GadgetContext {
> +  private final RemoteContentFetcher httpFetcher;
> +  public RemoteContentFetcher getHttpFetcher() {
> +    return httpFetcher;
> +  }
> +
> +  private final GadgetDataCache<MessageBundle> messageBundleCache;
> +  public GadgetDataCache<MessageBundle> getMessageBundleCache() {
> +    return messageBundleCache;
> +  }
> +
> +  private final Locale locale;
> +  public Locale getLocale() {
> +    return locale;
> +  }
> +
> +  /**
> +   * Creates a context for the current gadget.
> +   * @param httpFetcher
> +   * @param messageBundleCache
> +   * @param locale
> +   */
> +  public GadgetContext(RemoteContentFetcher httpFetcher,
> +                       GadgetDataCache<MessageBundle>  
> messageBundleCache,
> +                       Locale locale) {
> +    this.httpFetcher = httpFetcher;
> +    this.messageBundleCache = messageBundleCache;
> +    this.locale = locale;
> +  }
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetDataCache.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetDataCache.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetDataCache.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetDataCache.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,24 @@
> +/*
> + * Licensed 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;
> +
> +/**
> + * Simple interface for providing a data cache for objects of type  
> T keyed
> + * by a String.
> + * @param <T> Type of data to store in the cache
> + */
> +public interface GadgetDataCache<T> {
> +  public T get(String key) throws GadgetException;
> +  public void put(String key, T val) throws GadgetException;
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetException.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetException.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetException.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,62 @@
> +/*
> + * Licensed 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;
> +
> +/**
> + * Base class for all Gadget exceptions. The bulk of the code uses
> + * this class directly, differentiating between error conditions by
> + * the Code enumeration.
> + */
> +public class GadgetException extends Exception {
> +  public static enum Code {
> +    // Catch-all for internal errors
> +    INTERNAL_SERVER_ERROR,
> +
> +    // General xml
> +    EMPTY_XML_DOCUMENT,
> +    MALFORMED_XML_DOCUMENT,
> +
> +    FAILED_TO_RETRIEVE_CONTENT,
> +
> +    UNSUPPORTED_FEATURE,
> +
> +    // Interface component errors.
> +    MISSING_SPEC_CACHE,
> +    MISSING_MESSAGE_BUNDLE_CACHE,
> +    MISSING_REMOTE_OBJECT_FETCHER,
> +
> +    // Caja error
> +    MALFORMED_FOR_SAFE_INLINING
> +  }
> +
> +  private final Code code;
> +
> +  public GadgetException(Code code) {
> +    this.code = code;
> +  }
> +
> +  public GadgetException(Code code, Throwable cause) {
> +    super(cause);
> +    this.code = code;
> +  }
> +
> +  public GadgetException(Code code, String msg) {
> +    super(msg);
> +    this.code = code;
> +  }
> +
> +  public Code getCode() {
> +    return code;
> +  }
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeature.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeature.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeature.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeature.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,46 @@
> +/*
> + * Licensed 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 java.util.Map;
> +
> +/**
> + * Base interface providing Gadget Server's primary extensibility  
> mechanism.
> + *
> + * During processing of a {@code Gadget}, a tree of {@code  
> GadgetFeature}
> + * objects is constructed based on the &lt;Require&gt; and  
> &lt;Optional&gt;
> + * tags declared in its {@code GadgetSpec}, and the dependencies  
> registered
> + * for these in {@code GadgetFeatureRegistry}.
> + *
> + * Each {@code GadgetFeature}'s prepare method is called first -  
> potentially
> + * in parallel with many others whose dependencies have also been  
> satisfied.
> + * Once this has completed, its process method is called. Prepare  
> is useful
> + * for async operations such as retrieval of a remote resource; all
> + * {@code Gadget} modifications occur in process.
> + *
> + * To extend the Gadget Server's feature set, simply implement this  
> interface
> + * and register your class with {@code GadgetFeatureRegistry},  
> indicating
> + * which other {@code GadgetFeature} features are needed before  
> yours can
> + * operate successfully.
> + *
> + * Each feature <i>must</i> be instantiable by a no-argument  
> constructor,
> + * and will <i>always</i> be instantiated this way. As such, it is  
> recommended
> + * not to define a constructor for a feature at all.
> + */
> +public interface GadgetFeature {
> +  public void prepare(GadgetView gadget, GadgetContext context,
> +                      Map<String, String> params) throws  
> GadgetException;
> +  public void process(Gadget gadget, GadgetContext context,
> +                      Map<String, String> params) throws  
> GadgetException;
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeatureRegistry.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetFeatureRegistry.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeatureRegistry.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetFeatureRegistry.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,195 @@
> +/*
> + * Licensed 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 java.util.Arrays;
> +import java.util.HashMap;
> +import java.util.HashSet;
> +import java.util.LinkedList;
> +import java.util.List;
> +import java.util.Map;
> +import java.util.Set;
> +
> +/**
> + * Maintains a registry of all {@code GadgetFeature} types  
> supported by
> + * a given Gadget Server installation.
> + *
> + * To register a feature, simply create a static initialization  
> block in its
> + * class definition:
> + * <pre>
> + *   static {
> + *     GadgetFeatureRegistry.register("my.feature.name",
> + *                                    { "my.dep1", "my.dep2" },
> + *                                    MyFeature.class);
> + *   }
> + * </pre>
> + */
> +public class GadgetFeatureRegistry {
> +  private static final Map<String, Entry> features =
> +      new HashMap<String, Entry>();
> +  private static final List<Entry> core =
> +      new LinkedList<Entry>();
> +
> +  // Initialization of core components, providing a minimal base  
> context
> +  // in which all Gadgets operate. This set should be kept as  
> minimal as
> +  // possible. Anything added as registerCore will automatically  
> become a
> +  // dependency of every other feature.
> +  static {
> +    // Substitution jobs are not order-dependent, because the  
> actual order that
> +    // they are evaluated in is determined in Substitutions.java.  
> The order
> +    // defined here is not important.
> +    registerCore("core.msgbundlesubst", null,  
> MessageBundleSubstituter.class);
> +    registerCore("core.bidisubst", null, BidiSubstituter.class);
> +    registerCore("core.modulesubst", null, ModuleSubstituter.class);
> +    registerCore("core.userprefsubst", null,  
> UserPrefSubstituter.class);
> +
> +    // Core JS loading.
> +    registerCore("core.js", null, CoreJsFeature.class);
> +
> +    // These are all satisfied by core.
> +    register("setprefs", null, NoOpFeature.class);
> +  }
> +
> +  /**
> +   * Register a {@code GadgetFeature} identified by {@code name}  
> which
> +   * depends on other {@code GadgetFeature}s listed in {@code deps}
> +   * completing before this one does.
> +   *
> +   * Names are freeform, but it is strongly suggested that they are
> +   * namespaced, optionally (yet often usefully) in Java package- 
> notation ie.
> +   * 'com.google.gadgets.skins'.
> +   *
> +   * @param name Name of the feature to register, ideally using the  
> conventions
> +   * described
> +   * @param deps List of strings indicating features on which  
> {@code feature}
> +   * depends to operate correctly, which need to process the {@code  
> Gadget}
> +   * before it does
> +   * @param feature Class implementing the feature
> +   */
> +  public static void register(String name,
> +                              String[] deps,
> +                              Class<? extends GadgetFeature>  
> feature) {
> +    // Core entries must come first.
> +    Entry entry = new Entry(name, deps, feature);
> +    for (Entry coreEntry : core) {
> +      entry.deps.add(coreEntry.getName());
> +    }
> +    features.put(name, entry);
> +    validateFeatureGraph();
> +  }
> +
> +  /**
> +   * Registers a {@code GadgetFeature} which is <i>always</i> run  
> when
> +   * processing a {@code Gadget}, and on which all other features
> +   * implicitly depend. Use of this mechanism should be as sparing  
> as possible
> +   * to optimize performance.
> +   *
> +   * @param deps
> +   * @param cap
> +   */
> +  private static void registerCore(String name,
> +                                   String[] deps,
> +                                   Class<? extends GadgetFeature>  
> cap) {
> +    core.add(new Entry(name, deps, cap));
> +    validateFeatureGraph();
> +  }
> +
> +  /**
> +   * Traverses the graph traversed by the registered features,  
> validating
> +   * that it comprises a directed acyclic graph in which all  
> features'
> +   * dependencies are provided.
> +   *
> +   * If the graph is not acyclic, it cannot be used to create a  
> workflow. If
> +   * any dependencies are missing, {@code Gadget} rendering may be  
> incomplete.
> +   */
> +  private static void validateFeatureGraph() {
> +    // TODO: ensure that features form a DAG and that all deps are  
> provided
> +  }
> +
> +  /**
> +   * Attempts to retrieve all the {@code GadgetFeature} classes  
> specified
> +   * in the {@code needed} list. Those that are found are returned in
> +   * {@code resultsFound}, while the names of those that are  
> missing are
> +   * populated in {@code resultsMissing}.
> +   * @param needed List of names identifying features to retrieve
> +   * @param resultsFound List of feature entries found
> +   * @param resultsMissing List of feature identifiers that could  
> not be found
> +   * @return True if all features were retrieved
> +   */
> +  public static boolean getIncludedFeatures(List<String> needed,
> +                                            List<Entry> resultsFound,
> +                                            List<String>  
> resultsMissing) {
> +    resultsFound.clear();
> +    resultsMissing.clear();
> +    resultsFound.addAll(core);
> +    for (String featureName : needed) {
> +      Entry entry = features.get(featureName);
> +      if (entry == null) {
> +        resultsMissing.add(featureName);
> +      }
> +      resultsFound.add(entry);
> +    }
> +    return resultsMissing.size() == 0;
> +  }
> +
> +  public static class NoOpFeature implements GadgetFeature {
> +    public void prepare(GadgetView gadget, GadgetContext context,
> +        Map<String, String> params) {
> +    }
> +    public void process(Gadget gadget, GadgetContext context,
> +        Map<String, String> params) {
> +    }
> +  }
> +
> +  /**
> +   * Ties together a {@code GadgetFeature} with its name and  
> dependencies.
> +   */
> +  public static class Entry {
> +    private final String name;
> +    private final Set<String> deps;
> +    private final Class<? extends GadgetFeature> feature;
> +
> +    private Entry(String name,
> +                  String[] deps,
> +                  Class<? extends GadgetFeature> feature) {
> +      this.name = name;
> +      this.deps = new HashSet<String>();
> +      if (deps != null) {
> +        this.deps.addAll(Arrays.asList(deps));
> +      }
> +      this.feature = feature;
> +    }
> +
> +    /**
> +     * @return Name identifier
> +     */
> +    public String getName() {
> +      return name;
> +    }
> +
> +    /**
> +     * @return List of identifiers on which feature depends
> +     */
> +    public List<String> getDependencies() {
> +      return new LinkedList<String>(deps);
> +    }
> +
> +    /**
> +     * @return Class implementing the feature
> +     */
> +    public Class<? extends GadgetFeature> getFeature() {
> +      return feature;
> +    }
> +  }
> +}
>
> Added: 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=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetServer.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetServer.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,484 @@
> +/*
> + * Licensed 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 java.util.ArrayList;
> +import java.util.Arrays;
> +import java.util.HashSet;
> +import java.util.LinkedList;
> +import java.util.List;
> +import java.util.Locale;
> +import java.util.Map;
> +import java.util.Set;
> +import java.util.concurrent.Callable;
> +import java.util.concurrent.CompletionService;
> +import java.util.concurrent.ExecutionException;
> +import java.util.concurrent.Executor;
> +import java.util.concurrent.ExecutorCompletionService;
> +import java.util.concurrent.Future;
> +
> +public class GadgetServer {
> +  private final Executor executor;
> +  private GadgetDataCache<GadgetSpec> specCache;
> +  private GadgetDataCache<MessageBundle> messageBundleCache;
> +  private RemoteContentFetcher fetcher;
> +
> +  public GadgetServer(Executor executor) {
> +    this.executor = executor;
> +  }
> +
> +  public void setSpecCache(GadgetDataCache<GadgetSpec> specCache) {
> +    this.specCache = specCache;
> +  }
> +
> +  public void setMessageBundleCache(GadgetDataCache<MessageBundle>  
> cache) {
> +    messageBundleCache = cache;
> +  }
> +
> +  public void setContentFetcher(RemoteContentFetcher fetcher) {
> +    this.fetcher = fetcher;
> +  }
> +
> +  public static enum RenderingContext {
> +    CONTAINER, GADGET
> +  }
> +
> +  public Gadget processGadget(Gadget.ID gadgetId,
> +                              UserPrefs userPrefs,
> +                              Locale locale,
> +                              RenderingContext rctx)
> +      throws GadgetProcessException {
> +    if (specCache == null) {
> +      throw new  
> GadgetProcessException(GadgetException.Code.MISSING_SPEC_CACHE);
> +    }
> +    if (messageBundleCache == null ) {
> +      throw new GadgetProcessException(
> +          GadgetException.Code.MISSING_MESSAGE_BUNDLE_CACHE);
> +    }
> +    if (fetcher == null) {
> +      throw new GadgetProcessException(
> +          GadgetException.Code.MISSING_REMOTE_OBJECT_FETCHER);
> +    }
> +
> +    // Queue/tree of all jobs to be run for successful processing
> +    GadgetContext gc = new GadgetContext(fetcher,  
> messageBundleCache, locale);
> +    WorkflowContext wc = new WorkflowContext(gc);
> +
> +    // Bootstrap tree of jobs to process
> +    WorkflowDependency cacheLoadDep =
> +        new WorkflowDependency(WorkflowDependency.Type.CORE,  
> CACHE_LOAD);
> +    wc.jobsToRun.addJob(new CacheLoadTask(gadgetId, specCache),  
> cacheLoadDep);
> +
> +    WorkflowDependency urlFetchDep =
> +        new WorkflowDependency(WorkflowDependency.Type.CORE,  
> URL_FETCH);
> +    wc.jobsToRun.addJob(new SpecLoadTask(fetcher, gadgetId,  
> specCache),
> +                        urlFetchDep, cacheLoadDep);
> +
> +    WorkflowDependency enqueueFeatDep =
> +        new WorkflowDependency(WorkflowDependency.Type.CORE,  
> ENQUEUE_FEATURES);
> +    wc.jobsToRun.addJob(new EnqueueFeaturesTask(), enqueueFeatDep,  
> urlFetchDep);
> +
> +    // Instantiate CompletionService
> +    CompletionService<GadgetException> processor =
> +        new ExecutorCompletionService<GadgetException>(executor);
> +
> +    // All exceptions caught during processing
> +    List<GadgetException> gadgetExceptions = new  
> LinkedList<GadgetException>();
> +
> +    // Loop through queue of Callables, executing each in  
> CompletionService
> +    // whose precursors have been satisfied
> +    int jobsSubmitted = 0;
> +    do {
> +      // Loop through all jobs, submitting to run if all deps  
> satisfied
> +      List<WorkflowJob> runThisCycle = new LinkedList<WorkflowJob>();
> +      for (WorkflowJob candidate : wc.jobsToRun) {
> +        if (candidate.ready(wc.depsDone)) {
> +          runThisCycle.add(candidate);
> +        }
> +      }
> +
> +      // Fire off ready jobs and remove from jobsToRun list
> +      for (WorkflowJob runJob : runThisCycle) {
> +        processor.submit(runJob.task);
> +        jobsSubmitted++;
> +        wc.jobsToRun.remove(runJob);
> +      }
> +
> +      // Wait around for at least one job to have completed.
> +      // Completion of a job results in an additional dep added to  
> wc.depsDone,
> +      // thus potentially freeing up other jobs to run
> +      Future<GadgetException> latestResult = null;
> +      GadgetException gadgetException = null;
> +      try {
> +        latestResult = processor.take();
> +      } catch (InterruptedException e) {
> +        gadgetException = new GadgetException(
> +            GadgetException.Code.INTERNAL_SERVER_ERROR, e);
> +      }
> +
> +      // Ensure the task ran successfully
> +      if (latestResult != null) {
> +        try {
> +          gadgetException = latestResult.get();
> +        } catch (ExecutionException e) {
> +          // TODO: convert into gadgetException with internal error  
> type
> +          gadgetException = new GadgetException(
> +              GadgetException.Code.INTERNAL_SERVER_ERROR, e);
> +        } catch (InterruptedException e) {
> +          // TODO: convert into gadgetException with internal error  
> type
> +          gadgetException = new GadgetException(
> +              GadgetException.Code.INTERNAL_SERVER_ERROR, e);
> +        }
> +      }
> +
> +      if (gadgetException != null) {
> +        System.err.println(gadgetException.getCode().toString());
> +        gadgetException.printStackTrace();
> +        Throwable t = gadgetException.getCause();
> +        if (t != null) {
> +          t.printStackTrace();
> +        }
> +        // Add to list of all exceptions caught, clear jobs, and  
> continue
> +        // to aggressively catch as many exceptions as possible.  
> Since
> +        // tasks are running anyway, we may as well get their  
> results in
> +        // case it would be useful to the user.
> +        gadgetExceptions.add(gadgetException);
> +        wc.jobsToRun.clear();
> +        jobsSubmitted = wc.depsDone.size();
> +      }
> +    } while(wc.jobsToRun.size() > 0 || jobsSubmitted >  
> wc.depsDone.size());
> +
> +    if (gadgetExceptions.size() > 0) {
> +      throw new GadgetProcessException(gadgetExceptions);
> +    }
> +
> +    // terminate when all Callables are finished (or Exception  
> detected?)
> +    return wc.gadget;
> +  }
> +
> +  public List<GadgetSpec.UserPref> getPrefsInfo(Gadget.ID gadgetId) {
> +    return null;
> +  }
> +
> +  private static final String CACHE_LOAD = "cache-load";
> +  private static final String URL_FETCH = "url-fetch";
> +  private static final String ENQUEUE_FEATURES = "enqueue-features";
> +
> +  private static class WorkflowJobList extends  
> ArrayList<WorkflowJob> {
> +    private final WorkflowContext wc;
> +    public WorkflowJobList(WorkflowContext wc) {
> +      this.wc = wc;
> +    }
> +
> +    public void addJob(WorkflowTask task,
> +                       WorkflowDependency done,
> +                       WorkflowDependency... deps) {
> +      task.setup(wc, done);
> +      this.add(new WorkflowJob(task, deps));
> +    }
> +  }
> +
> +  private static class WorkflowDependency {
> +    private static enum Type {
> +      CORE, FEATURE_PREPARE, FEATURE_PROCESS
> +    }
> +
> +    private final Type type;
> +    private final String id;
> +
> +    private WorkflowDependency(Type type, String id) {
> +      this.type = type;
> +      this.id = id;
> +    }
> +
> +    @Override
> +    public boolean equals(Object other) {
> +      if (other instanceof WorkflowDependency) {
> +        WorkflowDependency wd = (WorkflowDependency)other;
> +        return type.equals(wd.type) && id.equals(wd.id);
> +      }
> +      return false;
> +    }
> +
> +    @Override
> +    public int hashCode() {
> +      return (type.toString() + id).hashCode();
> +    }
> +  }
> +
> +  private static class WorkflowContext {
> +    private Gadget gadget;
> +    private GadgetContext context;
> +    private WorkflowJobList jobsToRun;
> +    private final Set<WorkflowDependency> depsDone;
> +
> +    private WorkflowContext(GadgetContext context) {
> +      this.context = context;
> +      this.depsDone = new HashSet<WorkflowDependency>();
> +      this.jobsToRun = new WorkflowJobList(this);
> +    }
> +  }
> +
> +  private static class WorkflowJob {
> +    private final WorkflowTask task;
> +    private final List<WorkflowDependency> deps;
> +
> +    private WorkflowJob(WorkflowTask task, WorkflowDependency...  
> deps) {
> +      this.task = task;
> +      this.deps = new  
> LinkedList<WorkflowDependency>(Arrays.asList(deps));
> +    }
> +
> +    private boolean ready(Set<WorkflowDependency> depsDone) {
> +      for (WorkflowDependency dep : deps) {
> +        if (!depsDone.contains(dep)) {
> +          return false;
> +        }
> +      }
> +      return true;
> +    }
> +  }
> +
> +  private static class CacheLoadTask extends WorkflowTask {
> +    private final GadgetView.ID gadgetId;
> +    private final GadgetDataCache<GadgetSpec> specCache;
> +
> +    private CacheLoadTask(GadgetView.ID gadgetId,
> +                          GadgetDataCache<GadgetSpec> specCache) {
> +      this.gadgetId = gadgetId;
> +      this.specCache = specCache;
> +    }
> +
> +    @Override
> +    public void run(WorkflowContext wc) throws GadgetException {
> +      GadgetSpec spec = specCache.get(gadgetId.getKey());
> +      if (spec != null) {
> +        wc.gadget = new Gadget(gadgetId, spec);
> +      }
> +    }
> +  }
> +
> +  private static class SpecLoadTask extends WorkflowTask {
> +    private final RemoteContentFetcher fetcher;
> +    private final GadgetView.ID gadgetId;
> +    private final GadgetDataCache<GadgetSpec> specCache;
> +
> +    private SpecLoadTask(RemoteContentFetcher fetcher,  
> GadgetView.ID gadgetId,
> +                         GadgetDataCache<GadgetSpec> specCache) {
> +      this.fetcher = fetcher;
> +      this.gadgetId = gadgetId;
> +      this.specCache = specCache;
> +    }
> +
> +    @Override
> +    public void run(WorkflowContext wc) throws GadgetException {
> +      if (wc.gadget != null) {
> +        // Already retrieved: do nothing.
> +        return;
> +      }
> +
> +      byte[] xml = fetcher.fetch(gadgetId.getURL()).getByteArray();
> +      GadgetSpecParser specParser = new GadgetSpecParser();
> +      GadgetSpec spec = specParser.parse(gadgetId, xml);
> +      wc.gadget = new Gadget(gadgetId, spec);
> +      // This isn't a separate job because if it is we'd just need  
> another
> +      // flag telling us not to store to the cache.
> +      specCache.put(wc.gadget.getId().getKey(), wc.gadget.copy());
> +    }
> +  }
> +
> +  private static class EnqueueFeaturesTask extends WorkflowTask {
> +    @Override
> +    public void run(WorkflowContext wc) throws GadgetException {
> +      List<String> needed = new LinkedList<String>();
> +      Set<String> optionalNames = new HashSet<String>();
> +      Map<String, GadgetSpec.FeatureSpec> requires =  
> wc.gadget.getRequires();
> +      for (Map.Entry<String, GadgetSpec.FeatureSpec> entry :  
> requires.entrySet()) {
> +        needed.add(entry.getKey());
> +        if (entry.getValue().isOptional()) {
> +          optionalNames.add(entry.getKey());
> +        }
> +      }
> +
> +      // Retrieve needed feature processors from registry
> +      List<GadgetFeatureRegistry.Entry> resultsFound =
> +          new LinkedList<GadgetFeatureRegistry.Entry>();
> +      List<String> resultsMissing = new LinkedList<String>();
> +      GadgetFeatureRegistry.getIncludedFeatures(needed,
> +                                                resultsFound,
> +                                                resultsMissing);
> +
> +      // Classify features this server is missing
> +      List<String> missingRequired = new LinkedList<String>();
> +      List<String> missingOptional = new LinkedList<String>();
> +      for (String missingResult : resultsMissing) {
> +        if (optionalNames.contains(missingResult)) {
> +          missingOptional.add(missingResult);
> +        } else {
> +          missingRequired.add(missingResult);
> +        }
> +      }
> +
> +      if (missingRequired.size() > 0) {
> +        // TODO: throw MissingFeaturesException, subclass of  
> GadgetException,
> +        // which is then processed at termination of the jobs loop
> +        for (String missing : missingRequired) {
> +          System.err.println("Unsupported: " + missing);
> +        }
> +        throw new  
> GadgetException(GadgetException.Code.UNSUPPORTED_FEATURE);
> +      }
> +
> +      if (missingOptional.size() > 0) {
> +        // TODO: add custom task, dependent on nothing, adding  
> metadata re:
> +        // missing optionals to the gadget's output (satisfies  
> HasFeature(...))
> +      }
> +
> +      WorkflowDependency specLoadDep =
> +          new WorkflowDependency(WorkflowDependency.Type.CORE,  
> URL_FETCH);
> +      for (GadgetFeatureRegistry.Entry entry : resultsFound) {
> +        List<WorkflowDependency> prepareDeps =
> +            new LinkedList<WorkflowDependency>();
> +        List<WorkflowDependency> processDeps =
> +          new LinkedList<WorkflowDependency>();
> +
> +        // sanity check: each depends on the spec having been loaded
> +        prepareDeps.add(specLoadDep);
> +
> +        for (String featureDep : entry.getDependencies()) {
> +          // prepare depends on all its own deps...
> +          WorkflowDependency prepareNeedsDep =
> +              new  
> WorkflowDependency(WorkflowDependency.Type.FEATURE_PREPARE,
> +                                     featureDep);
> +          prepareDeps.add(prepareNeedsDep);
> +
> +          WorkflowDependency processNeedsDep =
> +            new  
> WorkflowDependency(WorkflowDependency.Type.FEATURE_PROCESS,
> +                                   featureDep);
> +          // Can't process until all dependencies prepare() and  
> process()
> +          // have completed.
> +          processDeps.add(prepareNeedsDep);
> +          processDeps.add(processNeedsDep);
> +        }
> +
> +        // Create task for prepare and process, each with the  
> dependency
> +        // that running each satisfies
> +        WorkflowDependency prepareDep =
> +            new  
> WorkflowDependency(WorkflowDependency.Type.FEATURE_PREPARE,
> +                                   entry.getName());
> +
> +        // We must guarantee that process is called after prepare.  
> This is
> +        // implicitly stating that process has all of prepare's  
> dependencies.
> +        processDeps.add(prepareDep);
> +        WorkflowDependency processDep =
> +          new  
> WorkflowDependency(WorkflowDependency.Type.FEATURE_PROCESS,
> +                                 entry.getName());
> +
> +        // Then add a new job for each task, with appropriate  
> execution
> +        // precursors/dependencies, to the jobs queue
> +        try {
> +          GadgetFeature feature = entry.getFeature().newInstance();
> +          wc.jobsToRun.addJob(new  
> FeaturePrepareTask(entry.getName(), feature),
> +                              prepareDep,
> +                              prepareDeps.toArray(new  
> WorkflowDependency[]{}));
> +          wc.jobsToRun.addJob(new  
> FeatureProcessTask(entry.getName(), feature),
> +                              processDep,
> +                              processDeps.toArray(new  
> WorkflowDependency[]{}));
> +        } catch (InstantiationException e) {
> +          System.err.println("Unable to instantiate "
> +                             + entry.getFeature().getName());
> +        } catch (IllegalAccessException e) {
> +          System.err.println("Unable to call no-arg constructor for "
> +                             + entry.getFeature().getName());
> +        }
> +      }
> +    }
> +  }
> +
> +  private static class FeaturePrepareTask extends WorkflowTask {
> +    private final GadgetFeature feature;
> +    private final String name;
> +    private FeaturePrepareTask(String name, GadgetFeature feature) {
> +      this.name = name;
> +      this.feature = feature;
> +    }
> +
> +    @Override
> +    public void run(WorkflowContext wc) throws GadgetException {
> +      Map<String, String> params =  
> Gadget.getFeatureParams(wc.gadget, name);
> +      feature.prepare(wc.gadget, wc.context, params);
> +    }
> +  }
> +
> +  private static class FeatureProcessTask extends WorkflowTask {
> +    private final GadgetFeature feature;
> +    private final String name;
> +    private FeatureProcessTask(String name, GadgetFeature feature) {
> +      this.name = name;
> +      this.feature = feature;
> +    }
> +
> +    @Override
> +    public void run(WorkflowContext wc) throws GadgetException {
> +      Map<String, String> params =  
> Gadget.getFeatureParams(wc.gadget, name);
> +      feature.process(wc.gadget,  wc.context,  params);
> +    }
> +  }
> +
> +  private static abstract class WorkflowTask
> +      implements Callable<GadgetException> {
> +    // This class is mostly just an alias to  
> Callable<GadgetException>
> +    // providing a helper method for passing in context
> +    public abstract void run(WorkflowContext wc) throws  
> GadgetException;
> +
> +    private WorkflowContext wc;
> +    private WorkflowDependency done;
> +    public WorkflowTask setup(WorkflowContext wc,  
> WorkflowDependency done) {
> +      this.wc = wc;
> +      this.done = done;
> +      return this;
> +    }
> +
> +    public GadgetException call() {
> +      GadgetException ret = null;
> +      try {
> +        this.run(wc);
> +      } catch (GadgetException e) {
> +        ret = e;
> +      } catch (Exception e) {
> +        // TODO: capture Throwable cause in wrapped exception
> +        ret = new GadgetException(
> +            GadgetException.Code.INTERNAL_SERVER_ERROR, e);
> +      } finally {
> +        wc.depsDone.add(done);
> +      }
> +      return ret;
> +    }
> +  }
> +
> +  public static class GadgetProcessException extends Exception {
> +    private final List<GadgetException> components;
> +
> +    public GadgetProcessException(List<GadgetException> components) {
> +      this.components = components;
> +    }
> +
> +    public GadgetProcessException(GadgetException.Code code) {
> +      this.components = new ArrayList<GadgetException>();
> +      this.components.add(new GadgetException(code));
> +    }
> +
> +    public List<GadgetException> getComponents() {
> +      return components;
> +    }
> +  }
> +}
>
> Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetSpec.java
> URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetSpec.java?rev=603539&view=auto
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> = 
> ======================================================================
> --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetSpec.java (added)
> +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/ 
> shindig/gadgets/GadgetSpec.java Wed Dec 12 02:33:35 2007
> @@ -0,0 +1,87 @@
> +/*
> + * Licensed 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 java.net.URL;
> +import java.util.List;
> +import java.util.Locale;
> +import java.util.Map;
> +
> +/**
> + * Represents a Gadget specification.
> + */
> +public interface GadgetSpec {
> +  public String getTitle();
> +  public URL getTitleURL();
> +  public String getDirectoryTitle();
> +  public String getDescription();
> +  public String getAuthor();
> +  public String getAuthorEmail();
> +  public String getScreenshot();
> +  public String getThumbnail();
> +
> +  public static interface MessageBundle {
> +    public Locale getLocale();
> +    public URL getURL();
> +    public boolean isRightToLeft();
> +  }
> +
> +  public List<MessageBundle> getMessageBundles();
> +
> +  public static interface FeatureSpec {
> +    public String getName();
> +    public Map<String, String> getParams();
> +    public boolean isOptional();
> +  }
> +
> +  public Map<String, FeatureSpec> getRequires();
> +  public List<String> getPreloads();
> +
> +  public static interface Icon {
> +    public URL getURL();
> +    public String getMode();
> +    public String getType();
> +  }
> +
> +  public List<Icon> getIcons();
> +
> +  public static interface UserPref {
> +
> +    public String getName();
> +    public String getDisplayName();
> +    public String getDefaultValue();
> +    public boolean isRequired();
> +
> +    public static enum DataType {
> +      STRING, HIDDEN, BOOL, ENUM, LIST, NUMBER
> +    }
> +
> +    public DataType getDataType();
> +  }
> +
> +  public List<UserPref> getUserPrefs();
> +
> +  public static enum ContentType {
> +      HTML, URL
> +  }
> +
> +  public ContentType getContentType();
> +  public URL getContentHref();
> +  public String getContentData();
> +
> +  /**
> +   * @return A copy of the spec. This is NOT the same as clone().
> +   */
> +  public GadgetSpec copy();
> +}
> \ No newline at end of file
>
>