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 2009/09/28 23:43:57 UTC

svn commit: r819743 - in /incubator/shindig/trunk/java: common/src/main/java/org/apache/shindig/common/servlet/ common/src/test/java/org/apache/shindig/common/servlet/ gadgets/src/main/java/org/apache/shindig/gadgets/ gadgets/src/main/java/org/apache/s...

Author: johnh
Date: Mon Sep 28 21:43:56 2009
New Revision: 819743

URL: http://svn.apache.org/viewvc?rev=819743&view=rev
Log:
JsFeatureLoader serving browser/UA-specific rpc.js. Only the "target" transport is emitted rather than redundant, unused alternate transports. The net result is improved latency.

This code should be treated as experimental, as extensive real-world testing of the feature has yet to be done. Further, a @Provider of UserAgents is needed by a given integrator.


Added:
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpServletUserAgentProvider.java
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/UserAgent.java
    incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/HttpServletUserAgentProviderTest.java
    incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/UserAgentTest.java
    incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoader.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoaderTest.java
Modified:
    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/servlet/JsServlet.java
    incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/JsFeatureLoaderTest.java

Added: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpServletUserAgentProvider.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpServletUserAgentProvider.java?rev=819743&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpServletUserAgentProvider.java (added)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpServletUserAgentProvider.java Mon Sep 28 21:43:56 2009
@@ -0,0 +1,51 @@
+/*
+ * 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.common.servlet;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Simple provider of UserAgent information from an HttpServletRequest.
+ * Uses an injected UserAgent.Parser to generate a UserAgent.Entry.
+ */
+public class HttpServletUserAgentProvider implements Provider<UserAgent> {
+  private final UserAgent.Parser uaParser;
+  private final Provider<HttpServletRequest> reqProvider;
+  
+  @Inject
+  public HttpServletUserAgentProvider(UserAgent.Parser uaParser,
+      Provider<HttpServletRequest> reqProvider) {
+    this.uaParser = uaParser;
+    this.reqProvider = reqProvider;
+  }
+
+  public UserAgent get() {
+    HttpServletRequest req = reqProvider.get();
+    if (req != null) {
+      String userAgent = req.getHeader("User-Agent");
+      if (userAgent != null) {
+        return uaParser.parse(userAgent);
+      }
+    }
+    return null;
+  }
+}

Added: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/UserAgent.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/UserAgent.java?rev=819743&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/UserAgent.java (added)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/UserAgent.java Mon Sep 28 21:43:56 2009
@@ -0,0 +1,79 @@
+/*
+ * 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.common.servlet;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Simple class defining basic User-Agent parsing.
+ * Defines an interface for a Parser, a list of common Browsers,
+ * and an Entry that is consumed by code providing UA-specific behavior.
+ */
+public final class UserAgent {
+  private final Browser browser;
+  private final String version;
+  private static final Pattern VERSION_NUMBER_REGEX = Pattern.compile(".*?([0-9]+(\\.[0-9]+)?).*");
+  
+  public UserAgent(Browser browser, String version) {
+    this.browser = browser;
+    this.version = version;
+  }
+  
+  /**
+   * @return Identifying browser string.
+   */
+  public Browser getBrowser() {
+    return browser;
+  }
+  
+  /**
+   * @return Version string of user agent.
+   */
+  public String getVersion() {
+    return version.trim();
+  }
+  
+  /**
+   * @return Numeric version number, if parseable. Otherwise -1.
+   */
+  public double getVersionNumber() {
+    // Attempt to retrieve the numeric part of a version string.
+    Matcher matcher = VERSION_NUMBER_REGEX.matcher(getVersion());
+    if (!matcher.matches()) {
+      return -1;
+    }
+    String matched = matcher.group(1);
+    return Double.parseDouble(matched);
+  }
+  
+  public interface Parser {
+    public UserAgent parse(String userAgent);
+  }
+  
+  public enum Browser {
+    MSIE,
+    FIREFOX,
+    SAFARI,
+    WEBKIT,
+    CHROME,
+    OPERA,
+    OTHER;
+  }
+}

Added: incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/HttpServletUserAgentProviderTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/HttpServletUserAgentProviderTest.java?rev=819743&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/HttpServletUserAgentProviderTest.java (added)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/HttpServletUserAgentProviderTest.java Mon Sep 28 21:43:56 2009
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.shindig.common.servlet;
+
+import com.google.inject.Provider;
+
+import static org.easymock.EasyMock.expect;
+
+import org.apache.shindig.common.EasyMockTestCase;
+import org.apache.shindig.common.servlet.HttpServletUserAgentProvider;
+import org.apache.shindig.common.servlet.UserAgent;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Simple test for HttpServletUserAgentProvider.
+ */
+public class HttpServletUserAgentProviderTest extends EasyMockTestCase {
+  private UserAgent.Parser parser = new PassThroughUAParser();
+  
+  public void testProviderWorks() {
+    String agentVersion = "AGENT_VERSION";
+    HttpServletRequest req = mock(HttpServletRequest.class);
+    expect(req.getHeader("User-Agent")).andReturn(agentVersion).once();
+    replay();
+    HttpServletUserAgentProvider provider = new HttpServletUserAgentProvider(
+        parser, new HttpServletRequestProvider(req));
+    UserAgent entry = provider.get();
+    assertEquals(UserAgent.Browser.OTHER, entry.getBrowser());
+    assertEquals(agentVersion, entry.getVersion().toString());
+    verify();
+  }
+  
+  public void testNoRequestGetsNull() {
+    HttpServletUserAgentProvider provider = new HttpServletUserAgentProvider(
+        parser, new HttpServletRequestProvider(null));
+    assertNull(provider.get());
+  }
+  
+  public void testNoUserAgentGetsNull() {
+    HttpServletRequest req = mock(HttpServletRequest.class);
+    expect(req.getHeader("User-Agent")).andReturn(null).once();
+    replay();
+    HttpServletUserAgentProvider provider = new HttpServletUserAgentProvider(
+        parser, new HttpServletRequestProvider(req));
+    assertNull(provider.get());
+    verify();
+  }
+  
+  private static class HttpServletRequestProvider implements Provider<HttpServletRequest> {
+    private HttpServletRequest req;
+    
+    private HttpServletRequestProvider(HttpServletRequest req) {
+      this.req = req;
+    }
+    
+    public HttpServletRequest get() {
+      return req;
+    }
+  }
+  
+  private static class PassThroughUAParser implements UserAgent.Parser {
+    public UserAgent parse(String agentVersion) {
+      return new UserAgent(UserAgent.Browser.OTHER, agentVersion);
+    }
+  }
+}

Added: incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/UserAgentTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/UserAgentTest.java?rev=819743&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/UserAgentTest.java (added)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/servlet/UserAgentTest.java Mon Sep 28 21:43:56 2009
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.common.servlet;
+
+import junit.framework.TestCase;
+
+public class UserAgentTest extends TestCase {
+  private UserAgent getUaEntry(String version) {
+    return new UserAgent(UserAgent.Browser.OTHER, version);
+  }
+  
+  public void testVersionNumberParsingStandard() {
+    assertEquals(3D, getUaEntry("3").getVersionNumber());
+  }
+  
+  public void testVersionNumberParsingStandardDecimal() {
+    assertEquals(3.1415, getUaEntry("3.1415").getVersionNumber());
+  }
+  
+  public void testVersionNumberParsingMultiPart() {
+    assertEquals(3.1, getUaEntry("3.1.5").getVersionNumber());
+  }
+  
+  public void testVersionNumberParsingAlphaSuffix() {
+    assertEquals(4.5, getUaEntry("4.5beta2").getVersionNumber()); 
+  }
+  
+  public void testVersionNumberParsingEmbeddedInTheMiddle() {
+    assertEquals(1.5, getUaEntry("beta 1.5 rc 5").getVersionNumber());
+  }
+}

Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoader.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoader.java?rev=819743&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoader.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoader.java Mon Sep 28 21:43:56 2009
@@ -0,0 +1,201 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.apache.shindig.common.servlet.UserAgent;
+import org.apache.shindig.gadgets.http.HttpFetcher;
+
+import java.util.Map;
+
+public class BrowserSpecificRpcJsFeatureLoader extends JsFeatureLoader {
+  private static final String RPC_FEATURE_NAME = "rpc";
+  private Provider<UserAgent> uaProvider;
+  private RpcJsLibrary rpcLib;
+  
+  @Inject
+  BrowserSpecificRpcJsFeatureLoader(HttpFetcher fetcher, Provider<UserAgent> uaProvider) {
+    super(fetcher);
+    this.uaProvider = uaProvider;
+  }
+  
+  @Override
+  protected JsLibrary createJsLibrary(JsLibrary.Type type, String content, String feature,
+      HttpFetcher fetcher) throws GadgetException {
+    if (feature.equals(RPC_FEATURE_NAME)) {
+      if (rpcLib == null) {
+        synchronized(this) {
+          if (rpcLib == null) {
+            rpcLib = new RpcJsLibrary(uaProvider, type, content);
+            return rpcLib;
+          }
+        }
+      }
+      return new NullJsLibrary(RPC_FEATURE_NAME);
+    }
+    return super.createJsLibrary(type, content, feature, fetcher);
+  }
+  
+  /**
+   * Do-nothing JsLibrary. Workaround for multi-file features
+   * with overrides, ie. rpc. createJsLibrary is called for each
+   * resource in the feature, but we want RpcJsLibrary to
+   * issue all the JS for the request.
+   */
+  protected static class NullJsLibrary extends JsLibrary {
+    public NullJsLibrary(String name) {
+      super(name, Type.FILE, "", "");
+    }
+  }
+  
+  /**
+   * Custom JsLibrary that emits only rpc.js transport code needed
+   * for a given User-Agent, which it gets from an injected Provider.
+   */
+  protected static class RpcJsLibrary extends JsLibrary {
+    private final Provider<UserAgent> uaProvider;
+    private final Map<String, String> rpcJsCode;
+    
+    private static final String RPC_JS_CORE = "rpc.js";
+    private static final String WPM_TX = "wpm.transport.js";
+    private static final String DPM_TX = "dpm.transport.js";
+    private static final String NIX_TX = "nix.transport.js";
+    private static final String FE_TX = "fe.transport.js";
+    private static final String RMR_TX = "rmr.transport.js";
+    private static final String IFPC_TX = "ifpc.transport.js";
+    private static final String ALL_TX = "all-transports";
+    
+    private static final String OPTIMIZED_SUFFIX = ":opt";
+    private static final String DEBUG_SUFFIX = ":dbg";
+    
+    protected RpcJsLibrary(Provider<UserAgent> uaProvider, Type type, String filePath) {
+      super(RPC_FEATURE_NAME, type, null, null);
+      this.uaProvider = uaProvider;
+      
+      // Something of a hack: filePath, without the trailing filename, gives the root
+      // path from which to load the JS.
+      int lastSlash = filePath.lastIndexOf('/');
+      if (lastSlash >= 0) {
+        filePath = filePath.substring(0, lastSlash + 1);
+      }
+      
+      // Load the core and all transport JS.
+      rpcJsCode = Maps.newHashMap();
+      StringBuilder allDbg = new StringBuilder();
+      StringBuilder allOpt = new StringBuilder();
+      for (String rpcPart :
+           ImmutableList.of(RPC_JS_CORE, WPM_TX, DPM_TX, NIX_TX, FE_TX, RMR_TX, IFPC_TX)) {
+        StringBuilder opt = new StringBuilder();
+        StringBuilder dbg = new StringBuilder();
+        loadOptimizedAndDebugData(rpcPart, type, opt, dbg);
+        rpcJsCode.put(rpcPart + OPTIMIZED_SUFFIX, opt.toString());
+        rpcJsCode.put(rpcPart + DEBUG_SUFFIX, dbg.toString());
+        if (!rpcPart.equals(RPC_JS_CORE)) {
+          allOpt.append(opt.toString());
+          allDbg.append(dbg.toString());
+        }
+      }
+      rpcJsCode.put(ALL_TX + OPTIMIZED_SUFFIX, allOpt.toString());
+      rpcJsCode.put(ALL_TX + DEBUG_SUFFIX, allDbg.toString());
+    }
+    
+    @Override
+    public String getContent() {
+      return getRpcContent(OPTIMIZED_SUFFIX);
+    }
+    
+    @Override
+    public String getDebugContent() {
+      return getRpcContent(DEBUG_SUFFIX);
+    }
+    
+    @Override
+    public boolean isProxyCacheable() {
+      return false;
+    }
+    
+    /**
+     * Does the dirty work of translating UserAgent into transport + rpc core.
+     * @param keySuffix Suffix appended to rpc content key getting debug or opt JS.
+     * @return Context-appropriate rpc.js
+     */
+    String getRpcContent(String keySuffix) {
+      // Send all by default.
+      String txKey = ALL_TX;
+      UserAgent userAgent = uaProvider.get();
+      if (userAgent != null) {
+        double version = userAgent.getVersionNumber();
+        switch(userAgent.getBrowser()) {
+          case MSIE:
+            if (version >= 8) {
+              txKey = WPM_TX;
+            } else if (version >= 6) {
+              txKey = NIX_TX;
+            }
+            break;
+          case FIREFOX:
+            if (version >= 3) {
+              txKey = WPM_TX;
+            } else if (version >= 2) {
+              txKey = FE_TX;
+            }
+            break;
+          case SAFARI:
+            if (version >= 4) {
+              txKey = WPM_TX;
+            } else if (version >= 2) {
+              txKey = RMR_TX;
+            }
+            break;
+          case CHROME:
+            if (version >= 2) {
+              txKey = WPM_TX;
+            } else {
+              txKey = RMR_TX;
+            }
+            break;
+          case OPERA:
+            if (version >= 9) {
+              txKey = WPM_TX;
+            } else if (version >= 8) {
+              txKey = DPM_TX;
+            }
+            break;
+          case WEBKIT:
+            if (version >= 29907) {
+              // Webkit nightlies have had window.postMessage since at least 2/1/2008.
+              // TODO Figure out exactly when it was added.
+              txKey = WPM_TX;
+            } else {
+              txKey = RMR_TX;
+            }
+            break;
+          case OTHER:
+            break;
+        }
+      }
+      
+      // Return the appropriate transport(s) + rpc core.
+      return rpcJsCode.get(txKey + keySuffix) + rpcJsCode.get(RPC_JS_CORE + keySuffix);
+    }
+  }
+}

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java?rev=819743&r1=819742&r2=819743&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/JsLibrary.java Mon Sep 28 21:43:56 2009
@@ -66,6 +66,13 @@
   public String getFeature() {
     return feature;
   }
+  
+  /**
+   * Indicates whether this feature is proxy-cacheable.
+   */
+  public boolean isProxyCacheable() {
+    return true;
+  }
 
   private static final Logger logger
       = Logger.getLogger("org.apache.shindig.gadgets");
@@ -124,8 +131,8 @@
     switch (type) {
       case FILE:
       case RESOURCE:
-        StringBuffer opt = new StringBuffer();
-        StringBuffer dbg = new StringBuffer();
+        StringBuilder opt = new StringBuilder();
+        StringBuilder dbg = new StringBuilder();
         loadOptimizedAndDebugData(content, type, opt, dbg);
         optimizedContent = opt.toString();
         debugContent = dbg.toString();
@@ -150,8 +157,8 @@
    * Helper method to load debug and optimized content from a path and type.
    * Only supports types FILE and RESOURCE.
    */
-  protected static void loadOptimizedAndDebugData(String content, Type type, StringBuffer opt,
-      StringBuffer dbg) {
+  protected static void loadOptimizedAndDebugData(String content, Type type, StringBuilder opt,
+      StringBuilder dbg) {
     String opt_data = null;
 
     if (content.endsWith(".js")) {

Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsServlet.java?rev=819743&r1=819742&r2=819743&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsServlet.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsServlet.java Mon Sep 28 21:43:56 2009
@@ -86,6 +86,7 @@
 
     Collection<GadgetFeature> features = registry.getFeatures(needed);
     StringBuilder jsData = new StringBuilder();
+    boolean isProxyCacheable = true;
     for (GadgetFeature feature : features) {
       for (JsLibrary lib : feature.getJsLibraries(context, container)) {
         if (lib.getType() != JsLibrary.Type.URL) {
@@ -94,6 +95,7 @@
           } else {
             jsData.append(lib.getContent());
           }
+          isProxyCacheable = isProxyCacheable && lib.isProxyCacheable();
           jsData.append(";\n");
         }
       }
@@ -107,10 +109,10 @@
 
     if (req.getParameter("v") != null) {
       // Versioned files get cached indefinitely
-      HttpUtil.setCachingHeaders(resp);
+      HttpUtil.setCachingHeaders(resp, !isProxyCacheable);
     } else {
       // Unversioned files get cached for 1 hour.
-      HttpUtil.setCachingHeaders(resp, 60 * 60);
+      HttpUtil.setCachingHeaders(resp, 60 * 60, !isProxyCacheable);
     }
     resp.setContentType("text/javascript; charset=utf-8");
     byte[] response = jsData.toString().getBytes("UTF-8");

Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoaderTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoaderTest.java?rev=819743&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoaderTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/BrowserSpecificRpcJsFeatureLoaderTest.java Mon Sep 28 21:43:56 2009
@@ -0,0 +1,46 @@
+/*
+ * 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.common.servlet.UserAgent;
+
+import com.google.inject.Provider;
+
+public class BrowserSpecificRpcJsFeatureLoaderTest extends JsFeatureLoaderTest {
+  private Provider<UserAgent> uaProvider = new TestUaProvider();
+  
+  @Override
+  protected JsFeatureLoader makeJsFeatureLoader() {
+    return new BrowserSpecificRpcJsFeatureLoader(fetcher, uaProvider);
+  }
+  
+  // All JsFeatureLoaderTests should continue to work. Add some
+  // additional tests for rpc.
+  
+  private static class TestUaProvider implements Provider<UserAgent> {
+    private UserAgent entry;
+
+    private void setNextToProvide(UserAgent entry) {
+      this.entry = entry;
+    }
+    
+    public UserAgent get() {
+      return entry;
+    }
+  }
+}
\ No newline at end of file

Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/JsFeatureLoaderTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/JsFeatureLoaderTest.java?rev=819743&r1=819742&r2=819743&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/JsFeatureLoaderTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/JsFeatureLoaderTest.java Mon Sep 28 21:43:56 2009
@@ -38,9 +38,9 @@
 import java.util.Map;
 
 public class JsFeatureLoaderTest extends EasyMockTestCase {
-  private final HttpFetcher fetcher = mock(HttpFetcher.class);
-  private final JsFeatureLoader loader = new JsFeatureLoader(fetcher);
-  private GadgetFeatureRegistry registry;
+  protected final HttpFetcher fetcher = mock(HttpFetcher.class);
+  protected JsFeatureLoader loader = makeJsFeatureLoader();
+  protected GadgetFeatureRegistry registry;
 
   private static final String FEATURE_NAME = "test";
   private static final String ALT_FEATURE_NAME = "test2";
@@ -55,12 +55,31 @@
     super.setUp();
     registry = new GadgetFeatureRegistry(null, fetcher, loader);
   }
+  
+  protected JsFeatureLoader makeJsFeatureLoader() {
+    return new JsFeatureLoader(fetcher);
+  }
 
-  private JsLibrary getJsLib(GadgetFeature feature) {
+  protected JsLibrary getJsLib(GadgetFeature feature) {
     return feature.getJsLibraries(
         RenderingContext.GADGET, ContainerConfig.DEFAULT_CONTAINER).get(0);
   }
 
+  protected File makeFeatureFile(String name, String content) throws Exception {
+    String xml = "<feature>" +
+                 "  <name>" + name + "</name>" +
+                 "  <gadget>" +
+                 "    <script>" + content + "</script>" +
+                 "  </gadget>" +
+                 "</feature>";
+    File file = File.createTempFile(getName(), name + ".xml");
+    file.deleteOnExit();
+    BufferedWriter out = new BufferedWriter(new FileWriter(file));
+    out.write(xml);
+    out.close();
+    return file;
+  }
+
   public void testBasicLoading() throws Exception {
     String xml = "<feature>" +
                  "  <name>" + FEATURE_NAME + "</name>" +
@@ -132,21 +151,6 @@
     assertEquals(FEATURE_NAME, lib.getFeature());
   }
 
-  private File makeFeatureFile(String name, String content) throws Exception {
-    String xml = "<feature>" +
-                 "  <name>" + name + "</name>" +
-                 "  <gadget>" +
-                 "    <script>" + content + "</script>" +
-                 "  </gadget>" +
-                 "</feature>";
-    File file = File.createTempFile(getName(), name + ".xml");
-    file.deleteOnExit();
-    BufferedWriter out = new BufferedWriter(new FileWriter(file));
-    out.write(xml);
-    out.close();
-    return file;
-  }
-
   public void testMultiplePaths() throws Exception {
     File file1 = makeFeatureFile(FEATURE_NAME, DEF_JS_CONTENT);
     File file2 = makeFeatureFile(ALT_FEATURE_NAME, ALT_JS_CONTENT);
@@ -175,7 +179,7 @@
         loader.loadFeatures(file1.getAbsolutePath() +
                             JsFeatureLoader.FILE_SEPARATOR +
                             file2.getAbsolutePath(), registry);
-    } catch (GadgetException e ) {
+    } catch (GadgetException e) {
         if (e.getCode() != GadgetException.Code.INVALID_PATH) {
             throw e;
         }