You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by et...@apache.org on 2008/06/13 21:36:57 UTC

svn commit: r667626 - in /incubator/shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/ main/java/org/apache/shindig/gadgets/spec/ test/java/org/apache/shindig/gadgets/

Author: etnu
Date: Fri Jun 13 12:36:56 2008
New Revision: 667626

URL: http://svn.apache.org/viewvc?rev=667626&view=rev
Log:
Applied patch for SHINDIG-363 from Patrick Fairbank, with some small modifications and extensive new tests for BasicGadgetSpecFactory.


Added:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ViewContentFetcher.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BasicGadgetSpecFactoryTest.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/ViewContentFetcherTest.java
Modified:
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.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/GadgetTestFixture.java

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java?rev=667626&r1=667625&r2=667626&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicGadgetSpecFactory.java Fri Jun 13 12:36:56 2008
@@ -18,10 +18,6 @@
  */
 package org.apache.shindig.gadgets;
 
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.google.inject.name.Named;
-
 import org.apache.shindig.common.cache.Cache;
 import org.apache.shindig.common.cache.LruCache;
 import org.apache.shindig.gadgets.http.HttpFetcher;
@@ -31,8 +27,15 @@
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.View;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
 import java.net.URI;
-import java.util.logging.Level;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
 /**
@@ -41,27 +44,32 @@
 @Singleton
 public class BasicGadgetSpecFactory implements GadgetSpecFactory {
 
-  private static final Logger logger
-      = Logger.getLogger(BasicGadgetSpecFactory.class.getName());
+  private static final Logger logger = Logger.getLogger(BasicGadgetSpecFactory.class.getName());
 
   private final HttpFetcher specFetcher;
-
   private final ContentRewriter rewriter;
-
+  private final Executor executor;
   private final long specMinTTL;
 
   // A cache of GadgetSpecs with expirations
   private final Cache<URI, SpecTimeoutPair> inMemorySpecCache;
 
-  public GadgetSpec getGadgetSpec(GadgetContext context)
-      throws GadgetException {
+  public GadgetSpec getGadgetSpec(GadgetContext context) throws GadgetException {
     return getGadgetSpec(context.getUrl(), context.getIgnoreCache());
   }
 
-  public GadgetSpec getGadgetSpec(URI gadgetUri, boolean ignoreCache)
-      throws GadgetException {
+  /**
+   * Retrieves a gadget specification from the cache or from the Internet.
+   */
+  public GadgetSpec getGadgetSpec(URI gadgetUri, boolean ignoreCache) throws GadgetException {
+    if (ignoreCache) {
+      return fetchGadgetSpecFromWeb(gadgetUri, true);
+    }
+
     GadgetSpec spec = null;
     long expiration = -1;
+
+    // Attempt to retrieve the gadget spec from the cache.
     synchronized (inMemorySpecCache) {
       SpecTimeoutPair gadgetSpecEntry = inMemorySpecCache.getElement(gadgetUri);
       if (gadgetSpecEntry != null) {
@@ -69,52 +77,91 @@
         expiration = gadgetSpecEntry.timeout;
       }
     }
-    if (ignoreCache || spec == null || expiration < System.currentTimeMillis()) {
+
+    // If the gadget spec is not in the cache or has expired, fetch it from its URI.
+    if (spec == null || expiration < System.currentTimeMillis()) {
       try {
-        HttpRequest request = HttpRequest.getRequest(
-            gadgetUri, ignoreCache);
-        HttpResponse response = specFetcher.fetch(request);
-        if (response.getHttpStatusCode() != HttpResponse.SC_OK) {
-          throw new GadgetException(
-              GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT,
-              "Unable to retrieve gadget xml. HTTP error " +
-                  response.getHttpStatusCode());
-        } else {
-          spec = new GadgetSpec(gadgetUri, response.getResponseAsString());
-          for (View v : spec.getViews().values()) {
-            if (v.getType() == View.ContentType.HTML && rewriter != null) {
-              v.setRewrittenContent(
-                  rewriter.rewriteGadgetView(spec, v.getContent(), "text/html"));
-            }
-          }
-          // Add the updated spec back to the cache and force the min TTL
-          expiration = Math
-              .max(response.getCacheExpiration(), System.currentTimeMillis() + specMinTTL);
-          synchronized (inMemorySpecCache) {
-            inMemorySpecCache.addElement(gadgetUri, new SpecTimeoutPair(spec, expiration));
-          }
-        }
-      } catch (GadgetException ge) {
+        return fetchGadgetSpecFromWeb(gadgetUri, false);
+      } catch (GadgetException e) {
         if (spec == null) {
-          throw ge;
+          throw e;
         } else {
-          logger.log(Level.WARNING,
-              "Gadget spec fetch failed for " + gadgetUri + " -  using cached ", ge);
+          logger.info("Gadget spec fetch failed for " + gadgetUri + " -  using cached ");
         }
       }
     }
+
+    return spec;
+  }
+
+  /**
+   * Retrieves a gadget specification from the Internet, processes its views and
+   * adds it to the cache.
+   */
+  private GadgetSpec fetchGadgetSpecFromWeb(URI gadgetUri, boolean ignoreCache)
+      throws GadgetException {
+    HttpRequest request = HttpRequest.getRequest(gadgetUri, ignoreCache);
+    HttpResponse response = specFetcher.fetch(request);
+    if (response.getHttpStatusCode() != HttpResponse.SC_OK) {
+      throw new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT,
+                                "Unable to retrieve gadget xml. HTTP error " +
+                                response.getHttpStatusCode());
+    }
+    GadgetSpec spec = new GadgetSpec(gadgetUri, response.getResponseAsString());
+
+    // Find the type=HTML views that link to their content externally.
+    List<View> hrefViewList = new ArrayList<View>();
+    for (View v : spec.getViews().values()) {
+      if (v.getType() != View.ContentType.URL && v.getHref() != null) {
+        hrefViewList.add(v);
+      }
+    }
+
+    // Retrieve all external view contents simultaneously.
+    CountDownLatch latch = new CountDownLatch(hrefViewList.size());
+    for (View v : hrefViewList) {
+      executor.execute(new ViewContentFetcher(v, latch, specFetcher, ignoreCache));
+    }
+    try {
+      latch.await();
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+
+    for (View v : spec.getViews().values()) {
+      if (v.getType() != View.ContentType.URL) {
+        // A non-null href at this point indicates that the retrieval of remote
+        // content has failed.
+        if (v.getHref() != null) {
+          throw new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT,
+                                    "Unable to retrieve remote gadget content.");
+        }
+        if (rewriter != null) {
+          v.setRewrittenContent(rewriter.rewriteGadgetView(spec, v.getContent(), "text/html"));
+        }
+      }
+    }
+
+    // Add the updated spec back to the cache and force the min TTL
+    long expiration = Math.max(
+        response.getCacheExpiration(), System.currentTimeMillis() + specMinTTL);
+    synchronized (inMemorySpecCache) {
+      inMemorySpecCache.addElement(gadgetUri, new SpecTimeoutPair(spec, expiration));
+    }
+
     return spec;
   }
 
   @Inject
   public BasicGadgetSpecFactory(HttpFetcher specFetcher,
-      ContentRewriter rewriter,
-      @Named("gadget-spec.cache.capacity")int gadgetSpecCacheCapacity,
-      @Named("gadget-spec.cache.minTTL")long minTTL) {
+                                ContentRewriter rewriter,
+                                Executor executor,
+                                @Named("gadget-spec.cache.capacity")int gadgetSpecCacheCapacity,
+                                @Named("gadget-spec.cache.minTTL")long minTTL) {
     this.specFetcher = specFetcher;
     this.rewriter = rewriter;
-    this.inMemorySpecCache = new LruCache<URI, SpecTimeoutPair>(
-        gadgetSpecCacheCapacity);
+    this.executor = executor;
+    this.inMemorySpecCache = new LruCache<URI, SpecTimeoutPair>(gadgetSpecCacheCapacity);
     this.specMinTTL = minTTL;
   }
 
@@ -127,4 +174,4 @@
       this.timeout = timeout;
     }
   }
-}
\ No newline at end of file
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ViewContentFetcher.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ViewContentFetcher.java?rev=667626&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ViewContentFetcher.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/ViewContentFetcher.java Fri Jun 13 12:36:56 2008
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets;
+
+import org.apache.shindig.gadgets.http.HttpFetcher;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.spec.View;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Logger;
+
+/**
+ * Retrieves the content pointed to by the href in a type=html view and inserts
+ * it into the view.
+ */
+public class ViewContentFetcher implements Runnable {
+  private static final Logger logger = Logger.getLogger(ViewContentFetcher.class.getName());
+  private final View view;
+  private final CountDownLatch latch;
+  private final HttpFetcher httpFetcher;
+  private final boolean ignoreCache;
+
+  public ViewContentFetcher(View view,
+                            CountDownLatch latch,
+                            HttpFetcher httpFetcher,
+                            boolean ignoreCache) {
+    this.view = view;
+    this.latch = latch;
+    this.httpFetcher = httpFetcher;
+    this.ignoreCache = ignoreCache;
+  }
+
+  /**
+   * Retrieves the remote view content.
+   */
+  public void run() {
+    HttpRequest request = HttpRequest.getRequest(view.getHref(), ignoreCache);
+    try {
+      HttpResponse response = httpFetcher.fetch(request);
+      if (response.getHttpStatusCode() != HttpResponse.SC_OK) {
+        throw new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT,
+                                  "Unable to retrieve gadget content. HTTP error " +
+                                  response.getHttpStatusCode());
+      } else {
+        view.setContent(response.getResponseAsString());
+
+        // Reset the href since the content is now inline; a non-null href will
+        // indicate a failed retrieval attempt.
+        view.setHref(null);
+      }
+    } catch (GadgetException e) {
+      logger.info("Failed to retrieve content at "  + view.getHref());
+    }
+    latch.countDown();
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java?rev=667626&r1=667625&r2=667626&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/View.java Fri Jun 13 12:36:56 2008
@@ -64,6 +64,9 @@
   public URI getHref() {
     return href;
   }
+  public void setHref(URI href) {
+    this.href = href;
+  }
 
   /**
    * Content@quirks
@@ -98,6 +101,9 @@
   public String getContent() {
     return content;
   }
+  public void setContent(String content) {
+    this.content = content;
+  }
 
   /**
    * Whether or not the content section has any __UP_ hangman variables.
@@ -190,9 +196,6 @@
     if (type == ContentType.URL && this.href == null) {
       throw new SpecParserException(
           "Content@href must be set when Content@type is \"url\".");
-    } else if (type == ContentType.HTML && this.href != null) {
-      throw new SpecParserException(
-          "Content@href must not be set when Content@type is not \"url\".");
     }
   }
 

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BasicGadgetSpecFactoryTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BasicGadgetSpecFactoryTest.java?rev=667626&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BasicGadgetSpecFactoryTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BasicGadgetSpecFactoryTest.java Fri Jun 13 12:36:56 2008
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.shindig.gadgets.http.HttpFetcher;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.rewrite.ContentRewriter;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+
+import com.google.common.collect.Maps;
+
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for BasicGadgetSpecFactory
+ */
+public class BasicGadgetSpecFactoryTest {
+  private final static URI SPEC_URL = URI.create("http://example.org/gadget.xml");
+  private final static URI REMOTE_URL = URI.create("http://example.org/remote.html");
+  private final static String LOCAL_CONTENT = "Hello, local content!";
+  private final static String ALT_LOCAL_CONTENT = "Hello, local content!";
+  private final static String REMOTE_CONTENT = "Hello, remote content!";
+  private final static String LOCAL_SPEC_XML
+      = "<Module>" +
+        "  <ModulePrefs title='GadgetSpecFactoryTest'/>" +
+        "  <Content type='html'>" + LOCAL_CONTENT + "</Content>" +
+        "</Module>";
+  private final static String ALT_LOCAL_SPEC_XML
+      = "<Module>" +
+        "  <ModulePrefs title='GadgetSpecFactoryTest'/>" +
+        "  <Content type='html'>" + ALT_LOCAL_CONTENT + "</Content>" +
+        "</Module>";
+  private final static String REMOTE_SPEC_XML
+      = "<Module>" +
+        "  <ModulePrefs title='GadgetSpecFactoryTest'/>" +
+        "  <Content type='html' href='" + REMOTE_URL + "'/>" +
+        "</Module>";
+  private final static String URL_SPEC_XML
+      = "<Module>" +
+        "  <ModulePrefs title='GadgetSpecFactoryTest'/>" +
+        "  <Content type='url' href='" + REMOTE_URL + "'/>" +
+        "</Module>";
+
+  private final static GadgetContext NO_CACHE_CONTEXT = new GadgetContext() {
+    @Override
+    public boolean getIgnoreCache() {
+      return true;
+    }
+    @Override
+    public URI getUrl() {
+      return SPEC_URL;
+    }
+  };
+  private final static Executor FAKE_EXECUTOR = new Executor() {
+    public void execute(Runnable runnable) {
+      runnable.run();
+    }
+  };
+
+  private final HttpFetcher fetcher = EasyMock.createNiceMock(HttpFetcher.class);
+  private final CaptureRewriter rewriter = new CaptureRewriter();
+
+  private final BasicGadgetSpecFactory specFactory
+      = new BasicGadgetSpecFactory(fetcher, rewriter, FAKE_EXECUTOR, 5, -1000);
+
+  @Test
+  public void specFetched() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    HttpResponse response = new HttpResponse(LOCAL_SPEC_XML);
+    expect(fetcher.fetch(request)).andReturn(response);
+    replay(fetcher);
+
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, true);
+
+    assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
+    assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getRewrittenContent());
+    assertTrue("Content not rewritten.", rewriter.rewroteView);
+  }
+
+  @Test
+  public void specFetchedWithContext() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    HttpResponse response = new HttpResponse(LOCAL_SPEC_XML);
+    expect(fetcher.fetch(request)).andReturn(response);
+    replay(fetcher);
+
+    GadgetSpec spec = specFactory.getGadgetSpec(NO_CACHE_CONTEXT);
+
+    assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
+    assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getRewrittenContent());
+    assertTrue("Content not rewritten.", rewriter.rewroteView);
+  }
+
+  @Test
+  public void staleSpecIsRefetched() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    Map<String, List<String>> headers = Maps.newHashMap();
+    headers.put("Pragma", Arrays.asList("no-cache"));
+    HttpResponse expiredResponse = new HttpResponse(
+        HttpResponse.SC_OK, LOCAL_SPEC_XML.getBytes("UTF-8"), headers);
+    HttpResponse updatedResponse = new HttpResponse(ALT_LOCAL_SPEC_XML);
+    expect(fetcher.fetch(request)).andReturn(expiredResponse).once();
+    expect(fetcher.fetch(request)).andReturn(updatedResponse).once();
+    replay(fetcher);
+
+    specFactory.getGadgetSpec(SPEC_URL, true);
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, false);
+
+    assertEquals(ALT_LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
+    assertEquals(ALT_LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getRewrittenContent());
+    assertTrue("Content not rewritten.", rewriter.rewroteView);
+  }
+
+  @Test
+  public void staleSpecReturnedFromCacheOnError() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    Map<String, List<String>> headers = Maps.newHashMap();
+    headers.put("Pragma", Arrays.asList("no-cache"));
+    HttpResponse expiredResponse = new HttpResponse(
+        HttpResponse.SC_OK, LOCAL_SPEC_XML.getBytes("UTF-8"), headers);
+    expect(fetcher.fetch(request)).andReturn(expiredResponse);
+    expect(fetcher.fetch(request)).andReturn(HttpResponse.notFound()).once();
+    replay(fetcher);
+
+    specFactory.getGadgetSpec(SPEC_URL, true);
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, false);
+
+    assertEquals(ALT_LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
+    assertEquals(ALT_LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getRewrittenContent());
+    assertTrue("Content not rewritten.", rewriter.rewroteView);
+  }
+
+  @Test
+  public void externalContentFetched() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    HttpResponse response = new HttpResponse(REMOTE_SPEC_XML);
+    HttpRequest viewRequest = HttpRequest.getRequest(REMOTE_URL, true);
+    HttpResponse viewResponse = new HttpResponse(REMOTE_CONTENT);
+    expect(fetcher.fetch(request)).andReturn(response);
+    expect(fetcher.fetch(viewRequest)).andReturn(viewResponse);
+    replay(fetcher);
+
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, true);
+
+    assertEquals(REMOTE_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
+    assertEquals(REMOTE_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getRewrittenContent());
+    assertTrue("Content not rewritten.", rewriter.rewroteView);
+  }
+
+  @Test
+  public void typeUrlNotFetchedRemote() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    HttpResponse response = new HttpResponse(URL_SPEC_XML);
+    expect(fetcher.fetch(request)).andReturn(response);
+    replay(fetcher);
+
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, true);
+
+    assertEquals(REMOTE_URL, spec.getView(GadgetSpec.DEFAULT_VIEW).getHref());
+    assertEquals("", spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
+    assertEquals(null, spec.getView(GadgetSpec.DEFAULT_VIEW).getRewrittenContent());
+    assertFalse("Content was rewritten for type=url.", rewriter.rewroteView);
+  }
+
+  @Test(expected = GadgetException.class)
+  public void badFetchThrows() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    expect(fetcher.fetch(request)).andReturn(HttpResponse.error());
+    replay(fetcher);
+
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, true);
+  }
+
+  @Test(expected = GadgetException.class)
+  public void badRemoteContentThrows() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    HttpResponse response = new HttpResponse(REMOTE_SPEC_XML);
+    HttpRequest viewRequest = HttpRequest.getRequest(REMOTE_URL, true);
+    expect(fetcher.fetch(request)).andReturn(response);
+    expect(fetcher.fetch(viewRequest)).andReturn(HttpResponse.error());
+    replay(fetcher);
+
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, true);
+  }
+
+  @Test(expected = GadgetException.class)
+  public void throwingFetcherRethrows() throws Exception {
+    HttpRequest request = HttpRequest.getRequest(SPEC_URL, true);
+    expect(fetcher.fetch(request)).andThrow(
+        new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT));
+    replay(fetcher);
+
+    GadgetSpec spec = specFactory.getGadgetSpec(SPEC_URL, true);
+  }
+
+  private static class CaptureRewriter implements ContentRewriter {
+    private boolean rewroteView = false;
+
+    public HttpResponse rewrite(HttpRequest request, HttpResponse original) {
+      return original;
+    }
+
+    public String rewriteGadgetView(GadgetSpec spec, String original, String mimeType) {
+      rewroteView = true;
+      return original;
+    }
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java?rev=667626&r1=667625&r2=667626&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java Fri Jun 13 12:36:56 2008
@@ -91,6 +91,22 @@
         gadget.getSpec().getModulePrefs().getTitle());
   }
 
+  public void testGadgetSpecLookupWithFetcherFailure() throws Exception {
+    HttpResponse resp = new HttpResponse(HttpResponse.SC_NOT_FOUND);
+
+    expect(fetcher.fetch(SPEC_REQUEST)).andReturn(resp);
+    replay();
+
+    try {
+      Gadget gadget = gadgetServer.processGadget(BASIC_CONTEXT);
+      fail("Expected a GadgetException for a failed http fetch.");
+    } catch (GadgetException e) {
+      // Expected for a bad gadget spec URI.
+    }
+
+    verify();
+  }
+
   public void testSubstitutionsDone() throws Exception {
     String gadgetXml
         = "<Module>" +
@@ -318,4 +334,59 @@
     }
     verify();
   }
+
+  public void testViewContentFetching() throws Exception {
+    URI viewUri = URI.create("http://example.org/content.htm");
+    String gadgetXml
+        = "<Module>" +
+          "  <ModulePrefs title=\"foo\">" +
+          "  </ModulePrefs>" +
+          "  <Content type=\"html\" href=\"" + viewUri +"\" view=\"bar\" />" +
+          "</Module>";
+    String content ="<h2>foo</h2>";
+
+    HttpResponse spec = new HttpResponse(gadgetXml);
+    expect(fetcher.fetch(SPEC_REQUEST)).andReturn(spec);
+
+    HttpRequest viewContentRequest = new HttpRequest(viewUri);
+    HttpResponse viewContentResponse = new HttpResponse(content);
+    expect(fetcher.fetch(viewContentRequest)).andReturn(viewContentResponse);
+
+    replay();
+
+    Gadget gadget = gadgetServer.processGadget(BASIC_CONTEXT);
+
+    verify();
+
+    assertNull(gadget.getSpec().getView("bar").getHref());
+    assertEquals(content, gadget.getSpec().getView("bar").getContent());
+  }
+
+  public void testViewContentFetchingWithBadHref() throws Exception {
+    URI viewUri = URI.create("http://example.org/nonexistantcontent.htm");
+    String gadgetXml
+        = "<Module>" +
+          "  <ModulePrefs title=\"foo\">" +
+          "  </ModulePrefs>" +
+          "  <Content type=\"html\" href=\"" + viewUri +"\" view=\"bar\" />" +
+          "</Module>";
+
+    HttpResponse spec = new HttpResponse(gadgetXml);
+    expect(fetcher.fetch(SPEC_REQUEST)).andReturn(spec);
+
+    HttpRequest viewContentRequest = new HttpRequest(viewUri);
+    HttpResponse viewContentResponse = new HttpResponse(HttpResponse.SC_NOT_FOUND);
+    expect(fetcher.fetch(viewContentRequest)).andReturn(viewContentResponse);
+
+    replay();
+
+    try {
+      Gadget gadget = gadgetServer.processGadget(BASIC_CONTEXT);
+      fail("Expected a GadgetException for a failed http fetch of remote gadget content.");
+    } catch (GadgetException e) {
+      // Expected for a bad content href URI.
+    }
+
+    verify();
+  }
 }

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java?rev=667626&r1=667625&r2=667626&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetTestFixture.java Fri Jun 13 12:36:56 2008
@@ -39,8 +39,6 @@
       = mock(ContentFetcherFactory.class);
   public final HttpFetcher fetcher = mock(HttpFetcher.class);
   public final GadgetBlacklist blacklist = mock(GadgetBlacklist.class);
-  public final GadgetSpecFactory specFactory =
-      new BasicGadgetSpecFactory(fetcher, new NoOpContentRewriter(), 0, 0L);
   public final MessageBundleFactory bundleFactory =
       new BasicMessageBundleFactory(fetcher, 0, 0L);
   public GadgetFeatureRegistry registry;
@@ -50,6 +48,8 @@
       r.run();
     }
   };
+  public final GadgetSpecFactory specFactory = new BasicGadgetSpecFactory(
+      fetcher, new NoOpContentRewriter(), executor, 0, 0L);
 
 
   public GadgetTestFixture() {

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/ViewContentFetcherTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/ViewContentFetcherTest.java?rev=667626&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/ViewContentFetcherTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/ViewContentFetcherTest.java Fri Jun 13 12:36:56 2008
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.apache.shindig.common.xml.XmlUtil;
+import org.apache.shindig.gadgets.http.HttpFetcher;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.spec.View;
+
+import org.easymock.EasyMock;
+import org.junit.Test;
+import org.w3c.dom.Element;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Tests for ViewContentFetcher
+ */
+public class ViewContentFetcherTest {
+  private final HttpFetcher fetcher = EasyMock.createNiceMock(HttpFetcher.class);
+  private final static URI CONTENT_URI = URI.create("http://example.com/content.htm");
+  private final static String CONTENT = "<h2>foo</h2>";
+  private final static String VIEW_XML = "<Content type='html' href='" + CONTENT_URI + "' />";
+
+  @Test
+  public void getViewContent() throws Exception {
+    List<Element> elementList = new ArrayList<Element>();
+    elementList.add(XmlUtil.parse(VIEW_XML));
+    View view = new View(null, elementList);
+    CountDownLatch latch = new CountDownLatch(1);
+
+    HttpResponse response = new HttpResponse(CONTENT);
+    expect(fetcher.fetch(isA(HttpRequest.class))).andReturn(response).once();
+
+    replay(fetcher);
+
+    ViewContentFetcher viewContentFetcher = new ViewContentFetcher(view, latch, fetcher, false);
+    viewContentFetcher.run();
+
+    verify(fetcher);
+
+    assertNull(view.getHref());
+    assertEquals(CONTENT, view.getContent());
+    assertEquals(0, latch.getCount());
+  }
+
+  @Test
+  public void httpFetchException() throws Exception {
+    List<Element> elementList = new ArrayList<Element>();
+    elementList.add(XmlUtil.parse(VIEW_XML));
+    View view = new View(null, elementList);
+    CountDownLatch latch = new CountDownLatch(1);
+
+    expect(fetcher.fetch(isA(HttpRequest.class))).andThrow(
+        new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR)).once();
+
+    replay(fetcher);
+
+    ViewContentFetcher viewContentFetcher = new ViewContentFetcher(view, latch, fetcher, false);
+    viewContentFetcher.run();
+
+    verify(fetcher);
+
+    assertEquals(CONTENT_URI, view.getHref());
+    assertEquals("", view.getContent());
+    assertEquals(0, latch.getCount());
+  }
+
+  @Test
+  public void httpResponseNotOk() throws Exception {
+    List<Element> elementList = new ArrayList<Element>();
+    elementList.add(XmlUtil.parse(VIEW_XML));
+    View view = new View(null, elementList);
+    CountDownLatch latch = new CountDownLatch(1);
+
+    HttpResponse response = new HttpResponse(HttpResponse.SC_NOT_FOUND);
+    expect(fetcher.fetch(isA(HttpRequest.class))).andReturn(response).once();
+
+    replay(fetcher);
+
+    ViewContentFetcher viewContentFetcher = new ViewContentFetcher(view, latch, fetcher, false);
+    viewContentFetcher.run();
+
+    verify(fetcher);
+
+    assertEquals(CONTENT_URI, view.getHref());
+    assertEquals("", view.getContent());
+    assertEquals(0, latch.getCount());
+  }
+}