You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by jo...@apache.org on 2010/03/03 20:08:55 UTC

svn commit: r918637 - in /shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/uri/ test/java/org/apache/shindig/gadgets/uri/

Author: johnh
Date: Wed Mar  3 19:08:55 2010
New Revision: 918637

URL: http://svn.apache.org/viewvc?rev=918637&view=rev
Log:
Introduces interface ProxytUriManager, to be used in the content proxy.
Like other UriManager classes, it provides a well-encapsulated API
between URI generation and consumption.

DefaultProxyUriManager is provided as a default implementation of same.
It provides a base implementation of ConcatUriManager that is
conceptually
consistent with all the other UriManager implementations provided. In
particular:
* Defines its own ContainerConfig "gadgets.uri.proxy" namespace with
"host" and
"path" values used for Host: and path assignment, respectively.
* Provides a symmetric makeUri/validateUri pair
* Provides a Versioner interface, in this case supporting batching, for
versioning URLs or batches of same. No default Versioner implementation
is
provided in this CL.

Neither the interface nor implementation are used in this CL, as the
intent is
to keep CLs as small as possible. A follow-up CL will change
ProxyServlet
to use the new interface, along with attendant rewriter changes.


Added:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManager.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/ProxyUriManager.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManagerTest.java

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManager.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManager.java?rev=918637&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManager.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManager.java Wed Mar  3 19:08:55 2010
@@ -0,0 +1,235 @@
+/*
+ * 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.uri;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.shindig.config.ContainerConfig;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.uri.UriBuilder;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.uri.UriCommon.Param;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Generates URIs for use by the Shindig proxy service.
+ * 
+ * URIs are generated on the host specified in ContainerConfig at key
+ * "gadgets.uri.proxy.host".
+ * 
+ * The remainder of the URL may reference either chained or query-style
+ * proxy syntax. The former is used when "gadgets.uri.proxy.path" has token
+ * "%chained_params%" in it.
+ * 
+ * Chained: Returned URI contains query params in its path, with the proxied
+ * resource's URI appended verbatim to the end. This enables proxied SWFs
+ * to perform proxied, relative-URI resource loads. Example:
+ * http://www.example.com/gadgets/proxy/&s&refresh=1&...&e/http://www.foo.com/img.gif
+ * 
+ * Query param: All params are provided on the query string. Example:
+ * http://www.example.com/gadgets/proxy?refresh=1&url=http://www.foo.com/img.gif&...
+ * 
+ * This implementation supports batched versioning as well. The old-style "fp"
+ * (fingerprint) parameter is not supported any longer; its functionality is assumed
+ * to be subsumed into the version param.
+ */
+public class DefaultProxyUriManager implements ProxyUriManager {
+  static final String PROXY_HOST_PARAM = "gadgets.uri.proxy.host";
+  static final String PROXY_PATH_PARAM = "gadgets.uri.proxy.path";
+  static final String CHAINED_PARAMS_START_BEACON = "&s&";
+  static final String CHAINED_PARAMS_END_BEACON = "&e";
+  static final String CHAINED_PARAMS_TOKEN = "%chained_params%";
+
+  private final ContainerConfig config;
+  private final Versioner versioner;
+  
+  @Inject
+  public DefaultProxyUriManager(ContainerConfig config,
+                                Versioner versioner) {
+    this.config = config;
+    this.versioner = versioner;
+  }
+  
+  public List<Uri> make(List<ProxyUri> resources, Integer forcedRefresh) {
+    List<Uri> result = Lists.newArrayListWithCapacity(resources.size());
+    
+    if (resources.size() == 0) {
+      return result;
+    }
+    
+    List<Uri> resourceUris = Lists.newArrayListWithCapacity(resources.size());
+    
+    for (ProxyUri puc : resources) {
+      resourceUris.add(puc.getResource());
+    }
+    
+    Map<Uri, String> versions = Maps.newHashMap();
+    if (versioner != null) {
+      List<String> versionList = versioner.version(resourceUris, resources.get(0).getContainer());
+      if (versionList.size() == resources.size()) {
+        // This should always be the case.
+        // Should we error if not, or just WARNING?
+        Iterator<String> versionIt = versionList.iterator();
+        for (ProxyUri puc : resources) {
+          versions.put(puc.getResource(), versionIt.next());
+        }
+      }
+    }
+    
+    for (ProxyUri puc : resources) {
+      result.add(makeProxiedUri(puc, forcedRefresh, versions.get(puc.getResource())));
+    }
+    
+    return result;
+  }
+
+  private Uri makeProxiedUri(ProxyUri puc, Integer forcedRefresh, String version) {
+    UriBuilder queryBuilder = new UriBuilder();
+    
+    // Add all params common to both chained and query syntax.
+    String container = puc.getContainer();
+    queryBuilder.addQueryParameter(Param.CONTAINER.getKey(), container);
+    queryBuilder.addQueryParameter(Param.GADGET.getKey(), puc.getGadget());
+    queryBuilder.addQueryParameter(Param.DEBUG.getKey(), puc.isDebug() ? "1" : "0");
+    queryBuilder.addQueryParameter(Param.NO_CACHE.getKey(), puc.isNoCache() ? "1" : "0");
+    if (forcedRefresh != null) {
+      queryBuilder.addQueryParameter(Param.REFRESH.getKey(), forcedRefresh.toString());
+    }
+    if (version != null) {
+      queryBuilder.addQueryParameter(Param.VERSION.getKey(), version);
+    }
+    
+    UriBuilder uri = new UriBuilder();
+    uri.setAuthority(getReqConfig(container, PROXY_HOST_PARAM));
+    
+    // Chained vs. query-style syntax is determined by the presence of CHAINED_PARAMS_TOKEN
+    String path = getReqConfig(container, PROXY_PATH_PARAM);
+    if (path.contains(CHAINED_PARAMS_TOKEN)) {
+      // Chained proxy syntax. Stuff query params into the path and append URI verbatim at the end
+      path = path.replace(CHAINED_PARAMS_TOKEN,
+          CHAINED_PARAMS_START_BEACON + queryBuilder.getQuery() + CHAINED_PARAMS_END_BEACON);
+      uri.setPath(path);
+      String uriStr = uri.toString();
+      String curUri = uriStr + (!uriStr.endsWith("/") ? "/" : "") + puc.getResource().toString();
+      return Uri.parse(curUri);
+    }
+    
+    // Query-style syntax. Use path as normal and append query params at the end.
+    queryBuilder.addQueryParameter(Param.URL.getKey(), puc.getResource().toString());
+    uri.setQuery(queryBuilder.getQuery());
+    uri.setPath(path);
+    
+    return uri.toUri();
+  }
+  
+  public ProxyUri process(Uri uriIn) throws GadgetException {
+    UriStatus status = UriStatus.BAD_URI;
+    Uri uri = null;
+    
+    // First determine if the URI is chained-syntax or query-style.
+    String container = uriIn.getQueryParameter(Param.CONTAINER.getKey());
+    String uriStr = null;
+    Uri queryUri = null;
+    if (container != null &&
+        config.getString(container, PROXY_PATH_PARAM) != null &&
+        config.getString(container, PROXY_PATH_PARAM).equalsIgnoreCase(uriIn.getPath())) {
+      // Query-style. Has container param and path matches.
+      uriStr = uriIn.getQueryParameter(Param.URL.getKey());
+      queryUri = uriIn;
+    } else {
+      // Check for chained query string in the path.
+      int start = uriIn.getPath().indexOf(CHAINED_PARAMS_START_BEACON);
+      int end = uriIn.getPath().indexOf(CHAINED_PARAMS_END_BEACON, start);
+      if (start >= 0 && end > start) {
+        // Looks like chained proxy syntax. Pull out params.
+        String queryStr =
+            uriIn.getPath().substring(start + CHAINED_PARAMS_START_BEACON.length(), end);
+        queryUri = new UriBuilder().setQuery(queryStr).toUri();
+        container = queryUri.getQueryParameter(Param.CONTAINER.getKey());
+        if (container != null) {
+          String proxyPath = config.getString(container, PROXY_PATH_PARAM);
+          if (proxyPath != null) {
+            String[] chainedChunks = proxyPath.split(CHAINED_PARAMS_TOKEN);
+            if (chainedChunks.length == 2) {
+              // Pull URI out of original inUri's full representation.
+              String fullProxyUri = uriIn.toString();
+              int sfxIx = fullProxyUri.indexOf(chainedChunks[1]);
+              if (sfxIx > 0) {
+                uriStr = fullProxyUri.substring(sfxIx + chainedChunks[1].length());
+                while (uriStr.startsWith("/")) {
+                  uriStr = uriStr.substring(1);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Parameter validation.
+    if (StringUtils.isEmpty(uriStr) || StringUtils.isEmpty(container)) {
+      throw new GadgetException(GadgetException.Code.MISSING_PARAMETER,
+          "Missing required parameter(s):" +
+          (StringUtils.isEmpty(uriStr) ? " " + Param.URL.getKey() : "") +
+          (StringUtils.isEmpty(container) ? " " + Param.CONTAINER.getKey() : ""),
+          HttpResponse.SC_BAD_REQUEST);
+    }
+    
+    String queryHost = config.getString(container, PROXY_HOST_PARAM);
+    if (queryHost == null ||
+        !queryHost.equalsIgnoreCase(uriIn.getAuthority())) {
+      throw new GadgetException(GadgetException.Code.INVALID_PATH, "Invalid proxy host",
+          HttpResponse.SC_BAD_REQUEST);
+    }
+    
+    try {
+      uri = Uri.parse(uriStr);
+    } catch (Exception e) {
+      // NullPointerException or InvalidArgumentException.
+      throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
+          "Invalid " + Param.URL.getKey() + ": " + uriStr, HttpResponse.SC_BAD_REQUEST);
+    }
+    
+    // URI is valid.
+    status = UriStatus.VALID_UNVERSIONED;
+
+    String version = queryUri.getQueryParameter(Param.VERSION.getKey());
+    if (versioner != null && version != null) {
+      status = versioner.validate(uri, container, version);
+    }
+    
+    return new ProxyUri(status, uri, queryUri);
+  }
+
+  private String getReqConfig(String container, String key) {
+    String val = config.getString(container, key);
+    if (val == null) {
+      throw new RuntimeException("Missing required container config key: " + key + " for " +
+          "container: " + container);
+    }
+    return val;
+  }
+}

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/ProxyUriManager.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/ProxyUriManager.java?rev=918637&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/ProxyUriManager.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/uri/ProxyUriManager.java Wed Mar  3 19:08:55 2010
@@ -0,0 +1,93 @@
+/*
+ * 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.uri;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetException;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+public interface ProxyUriManager {
+  /**
+   * Generate a Uri that proxies the given resource Uri.
+   * 
+   * @param gadget Context for the rewrite
+   * @param resource Resource Uri to proxy
+   * @param forcedRefresh Forced expires value to use for resource
+   * @return Uri of proxied resource
+   */
+  List<Uri> make(List<ProxyUri> resource, Integer forcedRefresh);
+  
+  public static class ProxyUri extends ProxyUriBase {
+    private final Uri resource;
+    
+    public ProxyUri(Gadget gadget, Uri resource) {
+      super(gadget);
+      this.resource = resource;
+    }
+    
+    public ProxyUri(UriStatus status, Uri resource, Uri base) {
+      super(status, base);
+      this.resource = resource;
+    }
+    
+    public Uri getResource() {
+      return resource;
+    }
+    
+    public static List<ProxyUri> fromList(Gadget gadget, List<Uri> uris) {
+      List<ProxyUri> res = Lists.newArrayListWithCapacity(uris.size());
+      for (Uri uri : uris) {
+        res.add(new ProxyUri(gadget, uri));
+      }
+      return res;
+    }
+  }
+  
+  /**
+   * Parse and validate the proxied Uri.
+   * 
+   * @param uri A Uri presumed to be a proxied Uri generated
+   *     by this class or in a compatible way
+   * @return Status of the Uri passed in
+   */
+  ProxyUri process(Uri uri) throws GadgetException;
+  
+  public interface Versioner {
+    /**
+     * Generates a version for each of the provided resources.
+     * @param resources Resources to version.
+     * @param container Container making the request
+     * @return Index-correlated list of version strings
+     */
+    List<String> version(List<Uri> resources, String container);
+    
+    /**
+     * Validate the version of the resource.
+     * @param resource Uri of a proxied resource
+     * @param container Container requesting the resource
+     * @param value Version value to validate.
+     * @return Status of the version.
+     */
+    UriStatus validate(Uri resource, String container, String value);
+  }
+}

Added: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManagerTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManagerTest.java?rev=918637&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManagerTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/uri/DefaultProxyUriManagerTest.java Wed Mar  3 19:08:55 2010
@@ -0,0 +1,301 @@
+/*
+ * 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.uri;
+
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.expect;
+import static org.easymock.classextension.EasyMock.eq;
+import static org.easymock.classextension.EasyMock.isA;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.apache.shindig.config.ContainerConfig;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.uri.UriBuilder;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.uri.UriCommon.Param;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class DefaultProxyUriManagerTest extends UriManagerTestBase {
+  private static final Uri RESOURCE_1 = Uri.parse("http://example.com/one.dat?param=value");
+  private static final Uri RESOURCE_2 = Uri.parse("http://gadgets.com/two.dat");
+  private static final Uri RESOURCE_3 = Uri.parse("http://foobar.com/three.dat");
+  
+  @Test
+  public void basicProxyQueryStyle() throws Exception {
+    checkQueryStyle(false, false, null);
+  }
+  
+  @Test
+  public void altParamsProxyQueryStyle() throws Exception {
+    checkQueryStyle(true, true, "version");
+  }
+  
+  private void checkQueryStyle(boolean debug, boolean noCache, String version) throws Exception {
+    String host = "host.com";
+    String path = "/proxy/path";
+    List<Uri> resources = ImmutableList.<Uri>of(RESOURCE_1);
+    List<Uri> uris = makeAndGet(host, path, debug, noCache, resources, version);
+    assertEquals(1, uris.size());
+    verifyQueryUri(RESOURCE_1, uris.get(0), debug, noCache, version);
+  }
+  
+  @Test
+  public void basicProxyChainedStyle() throws Exception {
+    checkChainedStyle(false, false, null);
+  }
+  
+  @Test
+  public void altParamsProxyChainedStyle() throws Exception {
+    checkChainedStyle(true, true, "version");
+  }
+  
+  private void checkChainedStyle(boolean debug, boolean noCache, String version) throws Exception {
+    String host = "host.com";
+    String path = "/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_TOKEN + "/path";
+    List<Uri> resources = ImmutableList.<Uri>of(RESOURCE_1);
+    List<Uri> uris = makeAndGet(host, path, debug, noCache, resources, version);
+    assertEquals(1, uris.size());
+    verifyChainedUri(RESOURCE_1, uris.get(0), debug, noCache, version, false);
+  }
+  
+  @Test
+  public void basicProxyChainedStyleEndOfPath() throws Exception {
+    checkChainedStyleEndOfPath(false, false, null);
+  }
+  
+  @Test
+  public void altParamsProxyChainedStyleEndOfPath() throws Exception {
+    checkChainedStyleEndOfPath(true, true, "version");
+  }
+  
+  private void checkChainedStyleEndOfPath(boolean debug, boolean noCache, String version) throws Exception {
+    String host = "host.com";
+    String path = "/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_TOKEN;
+    List<Uri> resources = ImmutableList.<Uri>of(RESOURCE_1);
+    List<Uri> uris = makeAndGet(host, path, debug, noCache, resources, version);
+    assertEquals(1, uris.size());
+    verifyChainedUri(RESOURCE_1, uris.get(0), debug, noCache, version, true);
+  }
+  
+  @Test
+  public void batchedProxyQueryStyle() throws Exception {
+    String host = "host.com";
+    String path = "/proxy/path";
+    List<Uri> resources = ImmutableList.<Uri>of(RESOURCE_1, RESOURCE_2, RESOURCE_3);
+    String[] versions = new String[] { "v1", "v2", "v3" };
+    List<Uri> uris = makeAndGet(host, path, true, true, resources, versions);
+    assertEquals(3, uris.size());
+    for (int i = 0; i < 3; ++i) {
+      verifyQueryUri(resources.get(i), uris.get(i), true, true, versions[i]);
+    }
+  }
+  
+  @Test
+  public void batchedProxyChainedStyle() throws Exception {
+    String host = "host.com";
+    String path = "/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_TOKEN + "/path";
+    List<Uri> resources = ImmutableList.<Uri>of(RESOURCE_1, RESOURCE_2, RESOURCE_3);
+    String[] versions = new String[] { "v1", "v2", "v3" };
+    List<Uri> uris = makeAndGet(host, path, true, true, resources, versions);
+    assertEquals(3, uris.size());
+    for (int i = 0; i < 3; ++i) {
+      verifyChainedUri(resources.get(i), uris.get(i), true, true, versions[i], false);
+    }
+  }
+  
+  @Test
+  public void validateQueryStyleUnversioned() throws Exception {
+    // Validate tests also serve as end-to-end tests: create, unpack.
+    checkValidate("/proxy/path", UriStatus.VALID_UNVERSIONED, null);
+  }
+  
+  @Test
+  public void validateChainedStyleUnversioned() throws Exception {
+    checkValidate("/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_TOKEN + "/path",
+        UriStatus.VALID_UNVERSIONED, null);
+  }
+  
+  @Test
+  public void validateQueryStyleVersioned() throws Exception {
+    checkValidate("/proxy/path", UriStatus.VALID_VERSIONED, "version");
+  }
+  
+  @Test
+  public void validateChainedStyleVersioned() throws Exception {
+    checkValidate("/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_TOKEN + "/path",
+        UriStatus.VALID_VERSIONED, "version");
+  }
+  
+  private void checkValidate(String path, UriStatus status, String version) throws Exception {
+    String host = "host.com";
+    // Pass null for status if version is null, since null version shouldn't result
+    // in a check to the versioner.
+    ProxyUriManager.Versioner versioner = makeVersioner(version == null ? null : status, version);
+    DefaultProxyUriManager manager = makeManager(host, path, versioner);
+    Gadget gadget = mockGadget(false, false);
+    List<Uri> resources = ImmutableList.of(RESOURCE_1);
+    List<Uri> uris = manager.make(
+        ProxyUriManager.ProxyUri.fromList(gadget, resources), 123);
+    assertEquals(1, uris.size());
+    ProxyUriManager.ProxyUri proxyUri = manager.process(uris.get(0));
+    assertEquals(RESOURCE_1, proxyUri.getResource());
+    assertEquals(CONTAINER, proxyUri.getContainer());
+    assertEquals(SPEC_URI.toString(), proxyUri.getGadget());
+    assertEquals(123, (int)proxyUri.getRefresh());
+    assertEquals(status, proxyUri.getStatus());
+    assertEquals(false, proxyUri.isDebug());
+    assertEquals(false, proxyUri.isNoCache());
+  }
+  
+  @Test(expected = GadgetException.class)
+  public void missingContainerParamQuery() throws Exception {
+    String host = "host.com";
+    String path = "/proxy/path";
+    DefaultProxyUriManager manager = makeManager(host, path, null);
+    Uri testUri = new UriBuilder().setAuthority(host).setPath(path)
+        .addQueryParameter(Param.URL.getKey(), "http://foo.com").toUri();
+    manager.process(testUri);
+  }
+  
+  @Test(expected = GadgetException.class)
+  public void missingContainerParamChained() throws Exception {
+    String host = "host.com";
+    String path = "/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_TOKEN + "/path";
+    DefaultProxyUriManager manager = makeManager(host, path, null);
+    Uri testUri = new UriBuilder().setAuthority(host).setPath(
+        "/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_START_BEACON + "&refresh=123" +
+        DefaultProxyUriManager.CHAINED_PARAMS_END_BEACON + "/path/http://foo.com").toUri();
+    manager.process(testUri);
+  }
+  
+  @Test(expected = GadgetException.class)
+  public void missingUrlQuery() throws Exception {
+    String host = "host.com";
+    String path = "/proxy/path";
+    DefaultProxyUriManager manager = makeManager(host, path, null);
+    Uri testUri = new UriBuilder().setAuthority(host).setPath(path)
+        .addQueryParameter(Param.CONTAINER.getKey(), CONTAINER).toUri();
+    manager.process(testUri);
+  }
+  
+  @Test(expected = GadgetException.class)
+  public void missingUrlChained() throws Exception {
+    String host = "host.com";
+    String path = "/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_TOKEN + "/path";
+    DefaultProxyUriManager manager = makeManager(host, path, null);
+    Uri testUri = new UriBuilder().setAuthority(host).setPath(
+        "/proxy/" + DefaultProxyUriManager.CHAINED_PARAMS_START_BEACON + "&container=" +
+        CONTAINER + DefaultProxyUriManager.CHAINED_PARAMS_END_BEACON + "/path/").toUri();
+    manager.process(testUri);
+  }
+  
+  @Test(expected = GadgetException.class)
+  public void invalidUrlParamQuery() throws Exception {
+    // Only test query style, since chained style should be impossible.
+    String host = "host.com";
+    String path = "/proxy/path";
+    DefaultProxyUriManager manager = makeManager(host, path, null);
+    Uri testUri = new UriBuilder().setAuthority(host).setPath(path)
+        .addQueryParameter(Param.CONTAINER.getKey(), CONTAINER)
+        .addQueryParameter(Param.URL.getKey(), "!^!").toUri();
+    manager.process(testUri);
+  }
+
+  private List<Uri> makeAndGet(String host, String path, boolean debug, boolean noCache,
+      List<Uri> resources, String... version) {
+    ProxyUriManager.Versioner versioner = makeVersioner(null, version);
+    DefaultProxyUriManager manager = makeManager(host, path, versioner);
+    Gadget gadget = mockGadget(debug, noCache);
+    return manager.make(
+        ProxyUriManager.ProxyUri.fromList(gadget, resources), 123);
+  }
+  
+  private void verifyQueryUri(Uri orig, Uri uri, boolean debug, boolean noCache, String version)
+      throws Exception {
+    assertEquals(version == null ? 6 : 7, uri.getQueryParameters().size());
+    assertEquals(SPEC_URI.toString(), uri.getQueryParameter(Param.GADGET.getKey()));
+    assertEquals(CONTAINER, uri.getQueryParameter(Param.CONTAINER.getKey()));
+    assertEquals(debug ? "1" : "0", uri.getQueryParameter(Param.DEBUG.getKey()));
+    assertEquals(noCache ? "1" : "0", uri.getQueryParameter(Param.NO_CACHE.getKey()));
+    assertEquals("123", uri.getQueryParameter(Param.REFRESH.getKey()));
+    assertEquals(orig.toString(), uri.getQueryParameter(Param.URL.getKey()));
+    if (version != null) {
+      assertEquals(version, uri.getQueryParameter(Param.VERSION.getKey()));
+    }
+  }
+  
+  private void verifyChainedUri(Uri orig, Uri uri, boolean debug, boolean noCache, String version,
+      boolean endOfPath)
+      throws Exception {
+    // Query params should copy over (even if none)
+    assertEquals(orig.getQueryParameters().size(), uri.getQueryParameters().size());
+    
+    // URI should end with the proxied content.
+    String uriStr = uri.toString();
+    assertTrue(uriStr.endsWith(orig.toString()));
+    
+    int proxyEnd = uriStr.indexOf("/proxy/") + "/proxy/".length();
+    String paramsUri = uriStr.substring(
+        proxyEnd +
+        DefaultProxyUriManager.CHAINED_PARAMS_START_BEACON.length(),
+        (endOfPath ? uriStr.indexOf("/", proxyEnd) : uriStr.indexOf("/path"))
+        - DefaultProxyUriManager.CHAINED_PARAMS_END_BEACON.length());
+    uri = new UriBuilder().setQuery(paramsUri).toUri();
+    assertEquals(version == null ? 5 : 6, uri.getQueryParameters().size());
+    assertEquals(SPEC_URI.toString(), uri.getQueryParameter(Param.GADGET.getKey()));
+    assertEquals(CONTAINER, uri.getQueryParameter(Param.CONTAINER.getKey()));
+    assertEquals(debug ? "1" : "0", uri.getQueryParameter(Param.DEBUG.getKey()));
+    assertEquals(noCache ? "1" : "0", uri.getQueryParameter(Param.NO_CACHE.getKey()));
+    assertEquals("123", uri.getQueryParameter(Param.REFRESH.getKey()));
+    if (version != null) {
+      assertEquals(version, uri.getQueryParameter(Param.VERSION.getKey()));
+    }
+  }
+  
+  private DefaultProxyUriManager makeManager(String host, String path,
+      ProxyUriManager.Versioner versioner) {
+    ContainerConfig config = createMock(ContainerConfig.class);
+    expect(config.getString(CONTAINER, DefaultProxyUriManager.PROXY_HOST_PARAM))
+        .andReturn(host).anyTimes();
+    expect(config.getString(CONTAINER, DefaultProxyUriManager.PROXY_PATH_PARAM))
+        .andReturn(path).anyTimes();
+    replay(config);
+    return new DefaultProxyUriManager(config, versioner);
+  }
+  
+  @SuppressWarnings("unchecked")
+  private ProxyUriManager.Versioner makeVersioner(UriStatus status, String... versions) {
+    ProxyUriManager.Versioner versioner = createMock(ProxyUriManager.Versioner.class);
+    expect(versioner.version(isA(List.class), eq(CONTAINER)))
+        .andReturn(Lists.newArrayList(versions)).anyTimes();
+    expect(versioner.validate(isA(Uri.class), eq(CONTAINER), isA(String.class)))
+        .andReturn(status).anyTimes();
+    replay(versioner);
+    return versioner;
+  }
+}