Author: jasvir
Date: Thu Dec  9 01:51:43 2010
New Revision: 1043815

URL: http://svn.apache.org/viewvc?rev=1043815&view=rev
Log:
Description
This is an endpoint that will be useful for inlining and for supporting safe
dynamic script tags and iframes.  Given a url, the end point returns santized
html, cajoled js and lint messages and errors.


Modified:
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
    
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
    
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java?rev=1043815&r1=1043814&r2=1043815&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/CajaContentRewriter.java
 Thu Dec  9 01:51:43 2010
@@ -18,14 +18,23 @@
  */
 package org.apache.shindig.gadgets.servlet;
 
+import com.google.caja.lexer.CharProducer;
 import com.google.caja.lexer.ExternalReference;
 import com.google.caja.lexer.FetchedData;
+import com.google.caja.lexer.FilePosition;
+import com.google.caja.lexer.HtmlLexer;
 import com.google.caja.lexer.InputSource;
+import com.google.caja.lexer.JsLexer;
+import com.google.caja.lexer.JsTokenQueue;
+import com.google.caja.lexer.ParseException;
 import com.google.caja.lexer.TokenConsumer;
 import com.google.caja.lexer.escaping.Escaping;
+import com.google.caja.parser.ParseTreeNode;
 import com.google.caja.parser.html.Dom;
+import com.google.caja.parser.html.DomParser;
 import com.google.caja.parser.html.Namespaces;
 import com.google.caja.parser.js.CajoledModule;
+import com.google.caja.parser.js.Parser;
 import com.google.caja.plugin.PipelineMaker;
 import com.google.caja.plugin.PluginCompiler;
 import com.google.caja.plugin.PluginMeta;
@@ -33,6 +42,7 @@ import com.google.caja.plugin.LoaderType
 import com.google.caja.plugin.UriEffect;
 import com.google.caja.plugin.UriFetcher;
 import com.google.caja.plugin.UriPolicy;
+import com.google.caja.plugin.UriFetcher.UriFetchException;
 import com.google.caja.render.Concatenator;
 import com.google.caja.render.JsMinimalPrinter;
 import com.google.caja.render.JsPrettyPrinter;
@@ -40,10 +50,14 @@ import com.google.caja.reporting.BuildIn
 import com.google.caja.reporting.Message;
 import com.google.caja.reporting.MessageContext;
 import com.google.caja.reporting.MessageLevel;
+import com.google.caja.reporting.MessagePart;
 import com.google.caja.reporting.MessageQueue;
+import com.google.caja.reporting.MessageType;
 import com.google.caja.reporting.RenderContext;
 import com.google.caja.reporting.SimpleMessageQueue;
 import com.google.caja.reporting.SnippetProducer;
+import com.google.caja.service.ServiceMessageType;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Maps;
 import com.google.inject.Inject;
 
@@ -68,7 +82,9 @@ import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -80,7 +96,7 @@ public class CajaContentRewriter impleme
   public static final String CAJOLED_DOCUMENTS = "cajoledDocuments";
 
   //class name for logging purpose
-  private static final String classname = 
"org.apache.shindig.gadgets.servlet.CajaContentRewriter";
+  private static final String classname = CajaContentRewriter.class.getName();
   private static final Logger LOG = 
Logger.getLogger(classname,MessageKeys.MESSAGES);
 
 
@@ -91,13 +107,109 @@ public class CajaContentRewriter impleme
   @Inject
   public CajaContentRewriter(CacheProvider cacheProvider, RequestPipeline 
requestPipeline,
       HtmlSerializer htmlSerializer) {
-    this.cajoledCache = cacheProvider.createCache(CAJOLED_DOCUMENTS);
+    if (null == cacheProvider) {
+      this.cajoledCache = null;
+    } else {
+      this.cajoledCache = cacheProvider.createCache(CAJOLED_DOCUMENTS);
+    }
     if (LOG.isLoggable(Level.INFO)) {
-      LOG.logp(Level.INFO, classname, "CajaContentRewriter", 
MessageKeys.CAJOLED_CACHE_CREATED, new Object[] {cajoledCache});
+      LOG.logp(Level.INFO, classname, "CajaContentRewriter", 
MessageKeys.CAJOLED_CACHE_CREATED,
+          new Object[] {cajoledCache});
     }
     this.requestPipeline = requestPipeline;
     this.htmlSerializer = htmlSerializer;
   }
+  
+  public class CajoledResult {
+    public final Node html;
+    public final CajoledModule js;
+    public final List<Message> messages;
+    public final boolean hasErrors;
+    CajoledResult(Node html, CajoledModule js, List<Message> messages, boolean 
hasErrors) {
+      this.html = html;
+      this.js = js;
+      this.messages = messages;
+      this.hasErrors= hasErrors;
+    }
+    public String toString() {
+      return "[html:'" + html + "', js: '" + js + "', messages: '" + messages 
+ "']"; 
+    }
+  }
+  
+  @VisibleForTesting
+  ParseTreeNode parse(InputSource is, CharProducer cp, String mime, 
MessageQueue mq)
+      throws ParseException {
+    ParseTreeNode ptn;
+    if (mime.contains("javascript")) {
+      JsLexer lexer = new JsLexer(cp);
+      JsTokenQueue tq = new JsTokenQueue(lexer, is);
+      if (tq.isEmpty()) { return null; }
+      Parser p = new Parser(tq, mq);
+      ptn = p.parse();
+      tq.expectEmpty();
+    } else {
+      DomParser p = new DomParser(new HtmlLexer(cp), false, is, mq);
+      ptn = new Dom(p.parseFragment());
+      p.getTokenQueue().expectEmpty();
+    }
+    return ptn;
+  }
+  
+  public CajoledResult rewrite(Uri uri, String container, String mime,
+      boolean es53, boolean debug) {
+    URI javaUri = uri.toJavaUri();
+    InputSource is = new InputSource(javaUri);
+    MessageQueue mq = new SimpleMessageQueue();
+    try {
+      UriFetcher fetcher = makeFetcher(uri, container);
+      ExternalReference extRef = new ExternalReference(javaUri,
+          FilePosition.instance(is, /*lineNo*/ 1, /*charInFile*/ 1, 
/*charInLine*/ 1));
+        // If the fetch fails, a UriFetchException is thrown and serialized as 
part of the
+        // message queue.
+        CharProducer cp = fetcher.fetch(extRef, mime).getTextualContent();
+        ParseTreeNode ptn = parse(is, cp, mime, mq);
+        return rewrite(uri, container, ptn, es53, debug);
+    } catch (UnsupportedEncodingException e) {
+      LOG.severe("Unexpected inability to recognize mime type: " + mime);
+      mq.addMessage(ServiceMessageType.UNEXPECTED_INPUT_MIME_TYPE,
+          MessagePart.Factory.valueOf(mime));
+    } catch (UriFetchException e) {
+      LOG.info("Failed to retrieve: " + e.toString());
+    } catch (ParseException e) {
+      mq.addMessage(MessageType.PARSE_ERROR, FilePosition.UNKNOWN);
+    }
+    return new CajoledResult(null, null, mq.getMessages(), /* hasErrors */ 
true);
+  }
+  
+  public CajoledResult rewrite(Uri gadgetUri, String container,
+      ParseTreeNode root, boolean es53, boolean debug) {
+    UriFetcher fetcher = makeFetcher(gadgetUri, container);
+    UriPolicy policy = makePolicy(gadgetUri);
+    URI javaGadgetUri = gadgetUri.toJavaUri();
+    MessageQueue mq = new SimpleMessageQueue();
+    MessageContext context = new MessageContext();
+    PluginMeta meta = new PluginMeta(fetcher, policy);
+    PluginCompiler compiler = makePluginCompiler(meta, mq);
+    compiler.setMessageContext(context);
+
+    if (debug) {
+      compiler.setGoals(compiler.getGoals()
+          .without(PipelineMaker.ONE_CAJOLED_MODULE)
+          .with(PipelineMaker.ONE_CAJOLED_MODULE_DEBUG));
+    }
+
+    compiler.addInput(root, javaGadgetUri);
+
+    boolean hasErrors = false;
+    if (!compiler.run()) {
+      hasErrors = true;
+    }
+
+    return new CajoledResult(compiler.getStaticHtml(), 
+        compiler.getJavascript(), 
+        compiler.getMessageQueue().getMessages(),
+        hasErrors);
+  }
 
   public void rewrite(Gadget gadget, MutableContent mc) {
     if (!cajaEnabled(gadget)) return;
@@ -123,37 +235,19 @@ public class CajaContentRewriter impleme
     }
 
     if (cajoledData == null) {
-      UriFetcher fetcher = makeFetcher(gadget);
-      UriPolicy policy = makePolicy(gadget);
-      URI javaGadgetUri = gadgetContext.getUrl().toJavaUri();
-      MessageQueue mq = new SimpleMessageQueue();
-      MessageContext context = new MessageContext();
-      PluginMeta meta = new PluginMeta(fetcher, policy);
-      PluginCompiler compiler = makePluginCompiler(meta, mq);
-
-      compiler.setMessageContext(context);
-
       if (debug) {
         // This will load cajita-debugmode.js
         gadget.addFeature("caja-debug");
-        compiler.setGoals(compiler.getGoals()
-                .without(PipelineMaker.ONE_CAJOLED_MODULE)
-                .with(PipelineMaker.ONE_CAJOLED_MODULE_DEBUG));
       }
       
-      InputSource is = new InputSource(javaGadgetUri);
-      boolean safe = false;
-
-      compiler.addInput(new Dom(root), javaGadgetUri);
-
-      try {
-        if (!compiler.run()) {
-          throw new GadgetException(
-              GadgetException.Code.MALFORMED_FOR_SAFE_INLINING,
-              "Gadget has compile errors");
-        }
+      InputSource is = new InputSource(gadgetContext.getUrl().toJavaUri());
+      // TODO(jasvir): Turn on es53 once gadgets apis support it
+      CajoledResult result =
+        rewrite(gadgetContext.getUrl(), gadgetContext.getContainer(),
+            new Dom(root), /* es53 */ false, debug);
+      if (!result.hasErrors) {
         StringBuilder scriptBody = new StringBuilder();
-        CajoledModule cajoled = compiler.getJavascript();
+        CajoledModule cajoled = result.js;
         TokenConsumer tc = debug
             ? new JsPrettyPrinter(new Concatenator(scriptBody))
             : new JsMinimalPrinter(new Concatenator(scriptBody));
@@ -163,7 +257,7 @@ public class CajaContentRewriter impleme
 
         tc.noMoreTokens();
 
-        Node html = compiler.getStaticHtml();
+        Node html = result.html;
 
         Element script = doc.createElementNS(
             Namespaces.HTML_NAMESPACE_URI, "script");
@@ -180,9 +274,10 @@ public class CajaContentRewriter impleme
         cajoledOutput.appendChild(doc.adoptNode(html));
         cajoledOutput.appendChild(tameCajaClientApi(doc));
         cajoledOutput.appendChild(doc.adoptNode(script));
-
-        Element messagesNode = formatErrors(doc, is, docContent, mq,
-            /* is invisible */ false);
+        
+        List<Message> messages = result.messages;
+        Element messagesNode = formatErrors(doc, is, docContent, messages,
+            /* invisible */ false);
         cajoledOutput.appendChild(messagesNode);
         if (cajoledCache != null && !debug) {
           cajoledCache.addElement(cacheKey, cajoledOutput);
@@ -191,22 +286,15 @@ public class CajaContentRewriter impleme
         cajoledData = cajoledOutput;
         createContainerFor(doc, cajoledData);
         mc.documentChanged();
-        safe = true;
         HtmlSerialization.attach(doc, htmlSerializer, null);
-      } catch (GadgetException e) {
+      } else {
         // There were cajoling errors
         // Content is only used to produce useful snippets with error messages
+        List<Message> messages = result.messages;
         createContainerFor(doc,
-            formatErrors(doc, is, docContent, mq, true /* visible */));
+            formatErrors(doc, is, docContent, messages, true /* visible */));
         mc.documentChanged();
-        logException("rewrite", e, mq);
-        safe = true;
-      } finally {
-        if (!safe) {
-          // Fail safe
-          mc.setContent("");
-          mc.documentChanged();
-        }
+        logException("rewrite", messages);
       }
     }
   }
@@ -216,15 +304,13 @@ public class CajaContentRewriter impleme
         "1".equals(gadget.getContext().getParameter("caja")));
   }
 
-  private UriFetcher makeFetcher(Gadget gadget) {
-    final Uri gadgetUri = gadget.getContext().getUrl();
-    final String container = gadget.getContext().getContainer();
-    
+  UriFetcher makeFetcher(final Uri gadgetUri, final String container) {
     return new UriFetcher() {
       public FetchedData fetch(ExternalReference ref, String mimeType)
           throws UriFetchException {
         if (LOG.isLoggable(Level.INFO)) {
-          LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.RETRIEVE_REFERENCE, new Object[] {ref.toString()});
+          LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.RETRIEVE_REFERENCE,
+              new Object[] {ref.toString()});
         }        
         Uri resourceUri = gadgetUri.resolve(Uri.fromJavaUri(ref.getUri()));
         HttpRequest request =
@@ -236,23 +322,23 @@ public class CajaContentRewriter impleme
               new InputSource(ref.getUri()));
         } catch (GadgetException e) {
           if (LOG.isLoggable(Level.INFO)) {
-            LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.FAILED_TO_RETRIEVE, new Object[] {ref.toString()});
+            LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.FAILED_TO_RETRIEVE,
+                new Object[] {ref.toString()});
           }
-          return null;
+          throw new UriFetchException(ref, mimeType, e);
         } catch (IOException e) {
           if (LOG.isLoggable(Level.INFO)) {
-            LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.FAILED_TO_READ, new Object[] {ref.toString()});
+            LOG.logp(Level.INFO, classname, "makeFetcher", 
MessageKeys.FAILED_TO_READ,
+                new Object[] {ref.toString()});
           }
-          return null;
+          throw new UriFetchException(ref, mimeType, e);
         }
       }
       
     };
   }
   
-  private UriPolicy makePolicy(Gadget gadget) {
-    final Uri gadgetUri = gadget.getContext().getUrl();
-
+  private UriPolicy makePolicy(final Uri gadgetUri) {
     return new UriPolicy() {
       public String rewriteUri(ExternalReference ref, UriEffect effect,
           LoaderType loader, Map<String, ?> hints) {
@@ -277,7 +363,7 @@ public class CajaContentRewriter impleme
   }
 
   private Element formatErrors(Document doc, InputSource is,
-      CharSequence orig, MessageQueue mq, boolean visible) {
+      CharSequence orig, List<Message> messages, boolean visible) {
     MessageContext mc = new MessageContext();
     Map<InputSource, CharSequence> originalSrc = Maps.newHashMap();
     originalSrc.put(is, orig);
@@ -290,7 +376,7 @@ public class CajaContentRewriter impleme
     if (!visible) {
       errElement.setAttribute("style", "display: none");
     }
-    for (Message msg : mq.getMessages()) {
+    for (Message msg : messages) {
       // Ignore LINT messages
       if (MessageLevel.LINT.compareTo(msg.getMessageLevel()) <= 0) {
         String snippet = sp.getSnippet(msg);
@@ -317,17 +403,15 @@ public class CajaContentRewriter impleme
     return scriptElement;
   }
 
-  private void logException(String methodname, Exception cause, MessageQueue 
mq) {
+  private void logException(String methodname, List<Message> messages) {
     StringBuilder errbuilder = new StringBuilder();
     MessageContext mc = new MessageContext();
-    if (cause != null) {
-      errbuilder.append(cause).append('\n');
-    }
-    for (Message m : mq.getMessages()) {
+    for (Message m : messages) {
       errbuilder.append(m.format(mc)).append('\n');
     }
     if (LOG.isLoggable(Level.INFO)) {
-      LOG.logp(Level.INFO, classname, methodname, 
MessageKeys.UNABLE_TO_CAJOLE, new Object[] {errbuilder});
+      LOG.logp(Level.INFO, classname, methodname, MessageKeys.UNABLE_TO_CAJOLE,
+          new Object[] {errbuilder});
     }
   }
   

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java?rev=1043815&r1=1043814&r2=1043815&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandler.java
 Thu Dec  9 01:51:43 2010
@@ -68,6 +68,8 @@ public class GadgetsHandler {
   @VisibleForTesting
   static final String FAILURE_PROXY = "Failed to get proxy data.";
   @VisibleForTesting
+  static final String FAILURE_CAJA = "Failed to cajole data.";
+  @VisibleForTesting
   static final String FAILURE_JS = "Failed to get js data.";
 
   private static final List<String> DEFAULT_METADATA_FIELDS =
@@ -77,8 +79,9 @@ public class GadgetsHandler {
 
   private static final List<String> DEFAULT_PROXY_FIELDS = 
ImmutableList.of("proxyUrl");
 
+  private static final List<String> DEFAULT_CAJA_FIELDS = 
ImmutableList.of("*");
   private static final List<String> DEFAULT_JS_FIELDS = 
ImmutableList.of("jsUrl");
-
+  
   private static final Logger LOG = 
Logger.getLogger(GadgetsHandler.class.getName());
 
   protected final ExecutorService executor;
@@ -151,6 +154,18 @@ public class GadgetsHandler {
     }.execute(request);
   }
 
+  @Operation(httpMethods = {"POST", "GET"}, path = "cajole")
+  public Map<String, GadgetsHandlerApi.BaseResponse> cajole(BaseRequestItem 
request)
+      throws ProtocolException {
+    return new AbstractExecutor() {
+      @Override
+      protected Callable<CallableData> createJob(String url, BaseRequestItem 
request)
+          throws ProcessingException {
+        return createCajaJob(url, request);
+      }
+    }.execute(request);
+  }
+
   @Operation(httpMethods = "GET", path = "/@metadata.supportedFields")
   public Set<String> supportedFields(RequestItem request) {
     return ImmutableSet.copyOf(beanFilter
@@ -175,6 +190,12 @@ public class GadgetsHandler {
         beanFilter.getBeanFields(GadgetsHandlerApi.ProxyResponse.class, 5));
   }
 
+  @Operation(httpMethods = "GET", path = "/@cajole.supportedFields")
+  public Set<String> cajaSupportedFields(RequestItem request) {
+    return ImmutableSet.copyOf(beanFilter
+        .getBeanFields(GadgetsHandlerApi.CajaResponse.class, 5));
+  }
+
   /**
    * Class to handle threaded reply.
    * Mainly it made to support filtering the id (url)
@@ -287,6 +308,22 @@ public class GadgetsHandler {
     };
   }
 
+  // Hook to override in sub-class.
+  protected Callable<CallableData> createCajaJob(final String url,
+      BaseRequestItem request) throws ProcessingException {
+    final CajaRequestData cajaRequest = new CajaRequestData(url, request);
+    return new Callable<CallableData>() {
+      public CallableData call() throws Exception {
+        try {
+          return new CallableData(url, handlerService.getCaja(cajaRequest));
+        } catch (Exception e) {
+          return new CallableData(url,
+            handlerService.createErrorResponse(null, e, FAILURE_CAJA));
+        }
+      }
+    };
+  }
+
 
   /**
    * Gadget context classes used to translate JSON BaseRequestItem into a more
@@ -492,6 +529,27 @@ public class GadgetsHandler {
     return type;
   }
 
+  protected class CajaRequestData extends AbstractRequest
+      implements GadgetsHandlerApi.CajaRequest {
+    private final String mimeType;
+    private final boolean debug;
+    
+    public CajaRequestData(String url, BaseRequestItem request)
+        throws ProcessingException {
+      super(url, request, DEFAULT_CAJA_FIELDS);
+      this.mimeType = request.getParameter("mime-type", "text/html");
+      this.debug = getBooleanParam(request, "debug");
+    }
+
+    public String getMimeType() {
+      return mimeType;
+    }
+
+    public boolean getDebug() {
+      return debug;
+    }
+  }
+
   protected class MetadataRequestData extends AbstractRequest
       implements GadgetsHandlerApi.MetadataRequest {
     protected final Locale locale;

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java?rev=1043815&r1=1043814&r2=1043815&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
 Thu Dec  9 01:51:43 2010
@@ -182,6 +182,43 @@ public class GadgetsHandlerApi {
     public String getToken();
   }
 
+  // TODO(jasvir): Support getRefresh and noCache
+  public interface CajaRequest extends BaseRequest {
+    public String getMimeType();
+    public boolean getDebug();
+  }
+
+  public interface CajaResponse extends BaseResponse {
+    public String getHtml();
+    public String getJs();
+    public List<Message> getMessages();
+  }
+  
+  public interface Message {
+    public MessageLevel getLevel();
+    public String getName();
+    public String getMessage();
+  }
+  
+  public enum MessageLevel {
+    UNKNOWN,
+    // Fine grained info about internal progress
+    LOG,
+    // Broad info about internal progress
+    SUMMARY,
+    // Information inferred about source files
+    INFERENCE,
+    // Indicative of a possible problem in an input source file
+    LINT,
+    // Indicative of a probable problem in an input source file
+    WARNING,
+    // Indicative of a problem which prevents production of usable output
+    // but progress should continue in case further messages shed more info
+    ERROR,
+    // Indicative of a problem that prevents usable further processing
+    FATAL_ERROR;
+  }
+
   public interface ProxyRequest extends BaseRequest {
     // The BaseRequest.url store the resource to proxy
     public String getGadget();

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java?rev=1043815&r1=1043814&r2=1043815&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
 Thu Dec  9 01:51:43 2010
@@ -18,6 +18,13 @@
  */
 package org.apache.shindig.gadgets.servlet;
 
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
@@ -25,6 +32,8 @@ import com.google.common.collect.Immutab
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
 
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.commons.codec.binary.Base64InputStream;
 import org.apache.commons.io.IOUtils;
 import org.apache.shindig.auth.SecurityToken;
@@ -40,6 +49,7 @@ import org.apache.shindig.gadgets.Render
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.process.ProcessingException;
 import org.apache.shindig.gadgets.process.Processor;
+import org.apache.shindig.gadgets.servlet.CajaContentRewriter.CajoledResult;
 import org.apache.shindig.gadgets.spec.Feature;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.spec.LinkSpec;
@@ -55,15 +65,14 @@ import org.apache.shindig.gadgets.uri.Pr
 import org.apache.shindig.protocol.conversion.BeanDelegator;
 import org.apache.shindig.protocol.conversion.BeanFilter;
 
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.servlet.http.HttpServletResponse;
+import com.google.caja.lexer.TokenConsumer;
+import com.google.caja.parser.html.Nodes;
+import com.google.caja.render.Concatenator;
+import com.google.caja.render.JsMinimalPrinter;
+import com.google.caja.render.JsPrettyPrinter;
+import com.google.caja.reporting.MessageContext;
+import com.google.caja.reporting.RenderContext;
+import com.google.caja.util.Lists;
 
 /**
  * Service that interfaces with the system to provide information about 
gadgets.
@@ -117,6 +126,7 @@ public class GadgetsHandlerService {
   protected final BeanDelegator beanDelegator;
   protected final long specRefreshInterval;
   protected final BeanFilter beanFilter;
+  protected final CajaContentRewriter cajaContentRewriter;
 
   @Inject
   public GadgetsHandlerService(TimeSource timeSource, Processor processor,
@@ -124,7 +134,7 @@ public class GadgetsHandlerService {
       ProxyUriManager proxyUriManager, JsUriManager jsUriManager, ProxyHandler 
proxyHandler,
       JsHandler jsHandler,
       @Named("shindig.cache.xml.refreshInterval") long specRefreshInterval,
-      BeanFilter beanFilter) {
+      BeanFilter beanFilter, CajaContentRewriter cajaContentRewriter) {
     this.timeSource = timeSource;
     this.processor = processor;
     this.iframeUriManager = iframeUriManager;
@@ -135,6 +145,7 @@ public class GadgetsHandlerService {
     this.jsHandler = jsHandler;
     this.specRefreshInterval = specRefreshInterval;
     this.beanFilter = beanFilter;
+    this.cajaContentRewriter = cajaContentRewriter;
 
     this.beanDelegator = new BeanDelegator(apiClasses, enumConversionMap);
   }
@@ -240,6 +251,73 @@ public class GadgetsHandlerService {
           HttpResponse.SC_INTERNAL_SERVER_ERROR);
     }
   }
+  
+  /**
+   * Convert message level to Shindig's serializable message type
+   */
+  public static GadgetsHandlerApi.MessageLevel convertMessageLevel(String 
name) {
+    try {
+      return GadgetsHandlerApi.MessageLevel.valueOf(name);
+    }
+    catch (Exception ex) {
+      return GadgetsHandlerApi.MessageLevel.UNKNOWN;
+    }
+  }
+
+  /**
+   * Convert messages from Caja's internal message type to Shindig's 
serializable message type
+   */
+  private List<GadgetsHandlerApi.Message> convertMessages(
+      List<com.google.caja.reporting.Message> msgs,
+      final MessageContext mc) {
+    List<GadgetsHandlerApi.Message> result = Lists.newArrayList(msgs.size());
+    for (final com.google.caja.reporting.Message m : msgs) {
+      MessageImpl msg = new MessageImpl(m.getMessageType().name(),
+          m.format(mc), convertMessageLevel(m.getMessageLevel().name()));
+      result.add(msg);
+    }
+    return result;
+  }
+
+  public GadgetsHandlerApi.CajaResponse getCaja(GadgetsHandlerApi.CajaRequest 
request)
+      throws ProcessingException {
+    verifyBaseParams(request, true);
+    Set<String> fields = beanFilter.processBeanFields(request.getFields());
+
+    HttpResponse httpResponse = null;
+    try {
+      MessageContext mc = new MessageContext();
+      CajoledResult result =
+        cajaContentRewriter.rewrite(request.getUrl(), request.getContainer(),
+            request.getMimeType(), true /* only support es53 */, 
request.getDebug());
+      String html = null;
+      String js = null;
+      if (!result.hasErrors && null != result.html) {
+        html = Nodes.render(result.html);
+      }
+      
+      if (!result.hasErrors && null != result.js) {
+        StringBuilder builder = new StringBuilder();
+        TokenConsumer tc = request.getDebug() ?
+            new JsPrettyPrinter(new Concatenator(builder))
+            : new JsMinimalPrinter(new Concatenator(builder));
+        RenderContext rc = new RenderContext(tc)
+            .withAsciiOnly(true)
+            .withEmbeddable(true);
+        result.js.render(rc);
+        rc.getOut().noMoreTokens();
+        js = builder.toString();
+      }
+      
+      // TODO(jasvir): Improve Caja responses expiration handling
+      return createCajaResponse(request.getUrl(), 
+          html, js, convertMessages(result.messages, mc), fields,
+          timeSource.currentTimeMillis() + specRefreshInterval);
+    } catch (IOException e) {
+      LOG.log(Level.WARNING, "Error creating cajoled response", e);
+      throw new ProcessingException("Error getting response content", 
HttpResponse.SC_INTERNAL_SERVER_ERROR);
+    }
+  }
 
   /**
    * Verify request parameter are defined.
@@ -247,13 +325,13 @@ public class GadgetsHandlerService {
   protected void verifyBaseParams(GadgetsHandlerApi.BaseRequest request, 
boolean checkUrl)
       throws ProcessingException {
     if (checkUrl && request.getUrl() == null) {
-      throw new ProcessingException("Missing url paramater", 
HttpResponse.SC_BAD_REQUEST);
+      throw new ProcessingException("Missing url parameter", 
HttpResponse.SC_BAD_REQUEST);
     }
     if (request.getContainer() == null) {
-      throw new ProcessingException("Missing container paramater", 
HttpResponse.SC_BAD_REQUEST);
+      throw new ProcessingException("Missing container parameter", 
HttpResponse.SC_BAD_REQUEST);
     }
     if (request.getFields() == null) {
-      throw new ProcessingException("Missing fields paramater", 
HttpResponse.SC_BAD_REQUEST);
+      throw new ProcessingException("Missing fields parameter", 
HttpResponse.SC_BAD_REQUEST);
     }
   }
 
@@ -482,4 +560,56 @@ public class GadgetsHandlerService {
               .build()),
       fields);
   }
+  
+  @VisibleForTesting
+  GadgetsHandlerApi.CajaResponse createCajaResponse(Uri uri,
+      String html, String js, List<GadgetsHandlerApi.Message> messages, 
+      Set<String> fields, Long expireMs) throws IOException {
+    ImmutableList.Builder<GadgetsHandlerApi.Message> msgBuilder =
+      ImmutableList.builder();
+    for (final GadgetsHandlerApi.Message m : messages) {
+      msgBuilder.add(
+        beanDelegator.createDelegator(null, GadgetsHandlerApi.Message.class,
+            ImmutableMap.<String, Object>of("name", m.getName(), 
+                "level", m.getLevel(), "message", m.getMessage())));
+    }
+
+    return (GadgetsHandlerApi.CajaResponse) beanFilter.createFilteredBean(
+        beanDelegator.createDelegator(null, 
GadgetsHandlerApi.CajaResponse.class,
+            ImmutableMap.<String, Object>builder()
+            .put("url", uri)
+            .put("html", BeanDelegator.nullable(html))
+            .put("js", BeanDelegator.nullable(js))
+            .put("messages", msgBuilder.build())
+            .put("error", BeanDelegator.NULL)
+            .put("responsetimems", timeSource.currentTimeMillis())
+            .put("expiretimems", BeanDelegator.nullable(expireMs))
+            .build()),
+            fields);
+  }
+  
+  private class MessageImpl implements GadgetsHandlerApi.Message {
+    private GadgetsHandlerApi.MessageLevel level;
+    private String message;
+    private String name;
+    
+    public MessageImpl(String name, String message, 
GadgetsHandlerApi.MessageLevel level) {
+      this.name = name;
+      this.message = message;
+      this.level = level;
+    }
+    
+    public GadgetsHandlerApi.MessageLevel getLevel() {
+      return level;
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    public String getName() {
+      return name;
+    }
+    
+  }
 }

Modified: 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java?rev=1043815&r1=1043814&r2=1043815&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
 Thu Dec  9 01:51:43 2010
@@ -40,6 +40,8 @@ import org.apache.shindig.gadgets.featur
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.http.HttpResponseBuilder;
 import org.apache.shindig.gadgets.process.ProcessingException;
+import org.apache.shindig.gadgets.servlet.GadgetsHandlerApi.Message;
+import org.apache.shindig.gadgets.servlet.GadgetsHandlerApi.MessageLevel;
 import org.apache.shindig.gadgets.uri.JsUriManager;
 import org.apache.shindig.gadgets.uri.ProxyUriManager;
 import org.apache.shindig.gadgets.uri.JsUriManager.JsUri;
@@ -78,6 +80,7 @@ public class GadgetsHandlerServiceTest e
   private final ProxyUriManager proxyUriManager = mock(ProxyUriManager.class);
   private final JsUriManager jsUriManager = mock(JsUriManager.class);
   private final ProxyHandler proxyHandler = mock(ProxyHandler.class);
+  private final CajaContentRewriter cajaContentRewriter = 
mock(CajaContentRewriter.class);
   private final JsHandler jsHandler = mock(JsHandler.class);
 
   private FakeSecurityTokenCodec tokenCodec;
@@ -88,7 +91,7 @@ public class GadgetsHandlerServiceTest e
     tokenCodec = new FakeSecurityTokenCodec();
     gadgetHandler = new GadgetsHandlerService(timeSource, processor, 
urlGenerator,
         tokenCodec, proxyUriManager, jsUriManager, proxyHandler, jsHandler,
-        SPEC_REFRESH_INTERVAL_MS, new BeanFilter());
+        SPEC_REFRESH_INTERVAL_MS, new BeanFilter(), cajaContentRewriter);
   }
 
   // Next test verify that the API data classes are configured correctly.
@@ -493,6 +496,37 @@ public class GadgetsHandlerServiceTest e
     replay();
     GadgetsHandlerApi.ProxyResponse response = gadgetHandler.getProxy(request);
   }
+  
+  @Test
+  public void testCreateCajaResponse() throws Exception {
+    String goldenEntries[][] = {{"name1", "LINT", "msg1"}, {"name2", "LINT", 
"msg2"}};
+    List<Message> goldenMessages = Lists.newArrayList();
+    
+    for (String[] goldenEntry : goldenEntries) {
+      Message m = mock(Message.class);
+      expect(m.getName()).andReturn(goldenEntry[0]);
+      expect(m.getLevel()).andReturn(MessageLevel.valueOf(goldenEntry[1]));
+      expect(m.getMessage()).andReturn(goldenEntry[2]);
+      goldenMessages.add(m);
+    }
+    replay();
+    
+    Uri jsUri = Uri.parse("http://www.shindig.com/js";);
+    GadgetsHandlerApi.CajaResponse jsResponse =
+        gadgetHandler.createCajaResponse(jsUri, "html", "js",
+            goldenMessages, ImmutableSet.of("*"), null);
+    BeanDelegator.validateDelegator(jsResponse);
+    
+    assertEquals("html", jsResponse.getHtml());
+    assertEquals("js", jsResponse.getJs());
+    List<Message> response = jsResponse.getMessages();
+    assertEquals(goldenMessages.size(), response.size());
+    for (int i=0; i < response.size(); i++) {
+      assertEquals(goldenEntries[i][0], response.get(i).getName());
+      assertEquals(goldenEntries[i][1], response.get(i).getLevel().name());
+      assertEquals(goldenEntries[i][2], response.get(i).getMessage());
+    }
+  }
 
   private GadgetsHandlerApi.TokenData createTokenData(String ownerId, String 
viewerId) {
     GadgetsHandlerApi.TokenData token = 
mock(GadgetsHandlerApi.TokenData.class);
@@ -545,6 +579,15 @@ public class GadgetsHandlerServiceTest e
     return request;
   }
 
+  private GadgetsHandlerApi.CajaRequest createCajaRequest(Uri url, String 
container,
+      List<String> fields) {
+    GadgetsHandlerApi.CajaRequest request = 
mock(GadgetsHandlerApi.CajaRequest.class);
+    EasyMock.expect(request.getFields()).andStubReturn(fields);
+    EasyMock.expect(request.getContainer()).andStubReturn(container);
+    EasyMock.expect(request.getUrl()).andStubReturn(url);
+    return request;
+  }
+
   private class FakeSecurityTokenCodec implements SecurityTokenCodec {
     public SecurityToken inputToken = null;
     public SecurityTokenException exc = null;

Modified: 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java?rev=1043815&r1=1043814&r2=1043815&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java
 Thu Dec  9 01:51:43 2010
@@ -17,10 +17,12 @@
 
 package org.apache.shindig.gadgets.servlet;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.shindig.auth.SecurityToken;
@@ -35,6 +37,7 @@ import org.apache.shindig.common.util.Fa
 import org.apache.shindig.gadgets.RenderingContext;
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.process.ProcessingException;
+import org.apache.shindig.gadgets.servlet.CajaContentRewriter.CajoledResult;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
 import org.apache.shindig.gadgets.uri.JsUriManager;
 import org.apache.shindig.gadgets.uri.ProxyUriManager;
@@ -55,15 +58,21 @@ import org.json.JSONObject;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.http.HttpServletResponse;
+import com.google.caja.lexer.CharProducer;
+import com.google.caja.lexer.InputSource;
+import com.google.caja.lexer.ParseException;
+import com.google.caja.reporting.MessageQueue;
+import com.google.caja.reporting.SimpleMessageQueue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
 
 public class GadgetsHandlerTest extends EasyMockTestCase {
   private static final String GADGET1_URL = FakeProcessor.SPEC_URL.toString();
   private static final String GADGET2_URL = FakeProcessor.SPEC_URL2 
.toString();
+  private static final Uri HTML_URL = 
Uri.parse("http://www.example.com/a.html";);
+  private static final Uri JS_URL = Uri.parse("http://www.example.com/a.js";);
   private static final String CONTAINER = "container";
   private static final String TOKEN = "_nekot_";
   private static final Long SPEC_REFRESH_INTERVAL = 123L;
@@ -76,6 +85,7 @@ public class GadgetsHandlerTest extends 
   private final ProxyUriManager proxyUriManager = mock(ProxyUriManager.class);
   private final JsUriManager jsUriManager = mock(JsUriManager.class);
   private final ProxyHandler proxyHandler = mock(ProxyHandler.class);
+  private final CajaContentRewriter cajaContentRewriter = 
mock(CajaContentRewriter.class);
   private final JsHandler jsHandler = mock(JsHandler.class);
 
   private Injector injector;
@@ -95,7 +105,7 @@ public class GadgetsHandlerTest extends 
     BeanFilter beanFilter = new BeanFilter();
     GadgetsHandlerService service = new GadgetsHandlerService(timeSource, 
processor,
         urlGenerator, codec, proxyUriManager, jsUriManager, proxyHandler, 
jsHandler,
-        SPEC_REFRESH_INTERVAL, beanFilter);
+        SPEC_REFRESH_INTERVAL, beanFilter, cajaContentRewriter);
     GadgetsHandler handler =
         new GadgetsHandler(new TestExecutorService(), service, beanFilter);
     registry = new DefaultHandlerRegistry(
@@ -150,6 +160,134 @@ public class GadgetsHandlerTest extends 
     }
   }
 
+  private JSONObject makeCajaRequest(String mime, String... uris)
+      throws JSONException {
+    JSONObject params = new JSONObject()
+      .put("container", CONTAINER)
+      .put("ids", ImmutableList.copyOf(uris));
+    
+    if (null != mime) {
+      params.put("mime-type", mime);
+    }
+    
+    return new JSONObject()
+      .put("id", "req1")
+      .put("method", "gadgets.cajole")
+      .put("params", params);
+  }
+
+  @Test
+  public void testCajaEmptyRequest() throws Exception {
+    registerGadgetsHandler(null);
+    JSONObject request = makeCajaRequest(null);
+    RpcHandler operation = registry.getRpcHandler(request);
+    Object empty = operation.execute(emptyFormItems, token, converter).get();
+    JsonAssert.assertJsonEquals("{}", converter.convertToString(empty));
+  }
+  
+  private CajoledResult cajole(Uri uri, String mime, String content) throws 
ParseException {
+    CajaContentRewriter rw = new CajaContentRewriter(null, null, null);
+    InputSource is = new InputSource(uri.toJavaUri());
+    MessageQueue mq = new SimpleMessageQueue();
+    CharProducer cp = CharProducer.Factory.create(new StringReader(content), 
is);
+    return rw.rewrite(uri, CONTAINER, rw.parse(is, cp, mime, mq), false, 
false);
+  }
+  
+  @Test
+  public void testCajaJsRequest() throws Exception {
+    registerGadgetsHandler(null);
+    Capture<Uri> uriCapture = new Capture<Uri>();
+    Capture<String> containerCapture = new Capture<String>();
+    Capture<String> mimeCapture = new Capture<String>();
+    
+    String goldenMime = "text/javascript";
+    
+    CajoledResult golden = cajole(JS_URL, goldenMime, "alert('hi');");
+    
+    EasyMock.expect(cajaContentRewriter.rewrite(
+        EasyMock.capture(uriCapture),
+        EasyMock.capture(containerCapture),
+        EasyMock.capture(mimeCapture),
+        EasyMock.eq(true),
+        EasyMock.anyBoolean()))
+        .andReturn(golden)
+        .anyTimes();
+    replay();
+
+    JSONObject request = makeCajaRequest(goldenMime, JS_URL.toString());
+    RpcHandler operation = registry.getRpcHandler(request);
+    Object result = operation.execute(emptyFormItems, token, converter).get();
+    
+    assertEquals(CONTAINER, containerCapture.getValue());
+    assertEquals(JS_URL, uriCapture.getValue());
+    assertTrue(mimeCapture.getValue().contains("javascript"));
+    
+    JSONObject response = new JSONObject(converter.convertToString(result));
+    assertTrue(response.has(JS_URL.toString()));
+    JSONObject cajaResponse = response.getJSONObject(JS_URL.toString());
+
+    // Pure js url - no html produced
+    assertFalse(cajaResponse.has("html"));
+    assertTrue(cajaResponse.has("js"));
+    assertTrue(cajaResponse.has("messages"));
+    assertTrue(cajaResponse.getString("js").contains("alert"));
+    assertTrue(cajaResponse.getJSONArray("messages").length() > 0);
+  }
+
+  @Test
+  public void testCajaHtmlRequest() throws Exception {
+    registerGadgetsHandler(null);
+    Capture<Uri> uriCapture = new Capture<Uri>();
+    Capture<String> containerCapture = new Capture<String>();
+    Capture<String> mimeCapture = new Capture<String>();
+    
+    String goldenMime = "text/html";
+    
+    CajoledResult golden = cajole(HTML_URL, goldenMime,
+        "<b>hello</b>world<script>evilFunc1()</script><div 
onclick='evilFunc2'></div>");
+    
+    EasyMock.expect(cajaContentRewriter.rewrite(
+        EasyMock.capture(uriCapture),
+        EasyMock.capture(containerCapture),
+        EasyMock.capture(mimeCapture),
+        EasyMock.eq(true),
+        EasyMock.anyBoolean()))
+        .andReturn(golden)
+        .anyTimes();
+    replay();
+
+    JSONObject request = makeCajaRequest(goldenMime, HTML_URL.toString());
+    RpcHandler operation = registry.getRpcHandler(request);
+    Object result = operation.execute(emptyFormItems, token, converter).get();
+    
+    assertEquals(CONTAINER, containerCapture.getValue());
+    assertEquals(HTML_URL, uriCapture.getValue());
+    assertTrue(mimeCapture.getValue().contains("html"));
+    
+    JSONObject response = new JSONObject(converter.convertToString(result));
+    assertTrue(response.has(HTML_URL.toString()));
+
+    JSONObject cajaResponse = response.getJSONObject(HTML_URL.toString());
+    assertTrue(cajaResponse.has("html"));
+    assertTrue(cajaResponse.has("js"));
+    assertTrue(cajaResponse.has("messages"));
+    
+    // HTML is sanitized
+    assertTrue(cajaResponse.getString("html").contains("<b>hello</b>world"));
+    assertFalse(cajaResponse.getString("html").contains("evilFunc"));
+    assertTrue(cajaResponse.getString("js").contains("evilFunc"));
+    assertTrue(cajaResponse.getJSONArray("messages").length() > 0);
+  }
+
+  @Test
+  public void testCajaES53Request() throws Exception {
+    registerGadgetsHandler(null);
+    JSONObject request = makeCajaRequest(null);
+    RpcHandler operation = registry.getRpcHandler(request);
+    Object empty = operation.execute(emptyFormItems, token, converter).get();
+    JsonAssert.assertJsonEquals("{}", converter.convertToString(empty));
+  }
+
   @Test
   public void testGetRenderingType() throws Exception {
     assertEquals(GadgetsHandlerApi.RenderingType.DEFAULT, 
GadgetsHandler.getRenderingType(null));
@@ -568,5 +706,4 @@ public class GadgetsHandlerTest extends 
     assertFalse(gadget1.has("error"));
     verify();
   }
-
 }


Reply via email to