Package org.apache.shindig.gadgets.servlet

Source Code of org.apache.shindig.gadgets.servlet.CajaContentRewriter

/*
* 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.servlet;

import com.google.caja.lexer.ExternalReference;
import com.google.caja.lexer.FetchedData;
import com.google.caja.lexer.InputSource;
import com.google.caja.lexer.TokenConsumer;
import com.google.caja.lexer.escaping.Escaping;
import com.google.caja.opensocial.GadgetRewriteException;
import com.google.caja.parser.html.Dom;
import com.google.caja.parser.html.Namespaces;
import com.google.caja.parser.js.CajoledModule;
import com.google.caja.plugin.PluginCompiler;
import com.google.caja.plugin.PluginMeta;
import com.google.caja.plugin.UriFetcher;
import com.google.caja.plugin.UriPolicy;
import com.google.caja.render.Concatenator;
import com.google.caja.render.JsMinimalPrinter;
import com.google.caja.render.JsPrettyPrinter;
import com.google.caja.reporting.BuildInfo;
import com.google.caja.reporting.Message;
import com.google.caja.reporting.MessageContext;
import com.google.caja.reporting.MessageLevel;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.reporting.RenderContext;
import com.google.caja.reporting.SimpleMessageQueue;
import com.google.caja.reporting.SnippetProducer;
import com.google.common.collect.Maps;
import com.google.inject.Inject;

import org.apache.commons.io.IOUtils;
import org.apache.shindig.common.cache.Cache;
import org.apache.shindig.common.cache.CacheProvider;
import org.apache.shindig.common.util.HashUtil;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.RequestPipeline;
import org.apache.shindig.gadgets.parse.HtmlSerialization;
import org.apache.shindig.gadgets.parse.HtmlSerializer;
import org.apache.shindig.gadgets.rewrite.GadgetRewriter;
import org.apache.shindig.gadgets.rewrite.MutableContent;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.logging.Logger;

/**
* A GadgetRewriter based on caja technology
*/
public class CajaContentRewriter implements GadgetRewriter {
  public static final String CAJOLED_DOCUMENTS = "cajoledDocuments";

  private static final Logger LOG = Logger.getLogger(CajaContentRewriter.class.getName());

  private final Cache<String, Element> cajoledCache;
  private final RequestPipeline requestPipeline;
  private final HtmlSerializer htmlSerializer;

  @Inject
  public CajaContentRewriter(CacheProvider cacheProvider, RequestPipeline requestPipeline,
      HtmlSerializer htmlSerializer) {
    this.cajoledCache = cacheProvider.createCache(CAJOLED_DOCUMENTS);
    LOG.info("Cajoled cache created" + cajoledCache);
    this.requestPipeline = requestPipeline;
    this.htmlSerializer = htmlSerializer;
  }

  public void rewrite(Gadget gadget, MutableContent mc) {
    if (!cajaEnabled(gadget)) return;

    GadgetContext gadgetContext = gadget.getContext();
    boolean debug = gadgetContext.getDebug();
    Document doc = mc.getDocument();

    // Serialize outside of MutableContent, to prevent a re-parse.
    String docContent = HtmlSerialization.serialize(doc);
    String cacheKey = HashUtil.checksum(docContent.getBytes());
    Node root = doc.createDocumentFragment();
    root.appendChild(doc.getDocumentElement());

    Node cajoledData = null;
    if (cajoledCache != null && !debug) {
      Element cajoledOutput = cajoledCache.getElement(cacheKey);
      if (cajoledOutput != null) {
        cajoledData = doc.adoptNode(cajoledOutput);
        createContainerFor(doc, cajoledData);
        mc.documentChanged();
      }
    }

    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);

      /**
       * TODO(jasvir): This can provide support for debugging with
       * cajita-debugmode.js but cajita-debugmode.js should be loaded
       * iff url param debug=1
       *
       *      if (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 GadgetRewriteException("Gadget has compile errors");
        }
        StringBuilder scriptBody = new StringBuilder();
        CajoledModule cajoled = compiler.getJavascript();
        TokenConsumer tc = debug
            ? new JsPrettyPrinter(new Concatenator(scriptBody))
            : new JsMinimalPrinter(new Concatenator(scriptBody));
        cajoled.render(new RenderContext(tc)
          .withAsciiOnly(true)
          .withEmbeddable(true));

        tc.noMoreTokens();
       
        Node html = compiler.getStaticHtml();

        Element script = doc.createElementNS(
            Namespaces.HTML_NAMESPACE_URI, "script");
        script.setAttributeNS(
            Namespaces.HTML_NAMESPACE_URI, "type", "text/javascript");
        script.appendChild(doc.createTextNode(scriptBody.toString()));
       

        Element cajoledOutput = doc.createElement("div");
        cajoledOutput.setAttribute("id", "cajoled-output");
        cajoledOutput.setAttribute("classes", "g___");
        cajoledOutput.setAttribute("style", "position: relative;");

        cajoledOutput.appendChild(doc.adoptNode(html));
        cajoledOutput.appendChild(tameCajaClientApi(doc));
        cajoledOutput.appendChild(doc.adoptNode(script));

        Element messagesNode = formatErrors(doc, is, docContent, mq,
            /* is invisible */ false);
        cajoledOutput.appendChild(messagesNode);
        if (cajoledCache != null && !debug) {
          cajoledCache.addElement(cacheKey, cajoledOutput);
        }
        safe = true;
        cajoledData = cajoledOutput;
        createContainerFor(doc, cajoledData);
        mc.documentChanged();
        safe = true;
        HtmlSerialization.attach(doc, htmlSerializer, null);
      } catch (GadgetRewriteException e) {
        // There were cajoling errors
        // Content is only used to produce useful snippets with error messages
        createContainerFor(doc,
            formatErrors(doc, is, docContent, mq, true /* visible */));
        logException(e, mq);
        safe = true;
      } finally {
        if (!safe) {
          // Fail safe
          mc.setContent("");
          return;
        }
      }
    }
  }

  private boolean cajaEnabled(Gadget gadget) {
    return (gadget.getAllFeatures().contains("caja") ||
        "1".equals(gadget.getContext().getParameter("caja")));
  }

  private UriFetcher makeFetcher(Gadget gadget) {
    final Uri gadgetUri = gadget.getContext().getUrl();
    final String container = gadget.getContext().getContainer();
   
    return new UriFetcher() {
      public FetchedData fetch(ExternalReference ref, String mimeType)
          throws UriFetchException {
        LOG.info("Retrieving " + ref.toString());
        Uri resourceUri = gadgetUri.resolve(Uri.fromJavaUri(ref.getUri()));
        HttpRequest request =
            new HttpRequest(resourceUri).setContainer(container).setGadget(gadgetUri);
        try {
          HttpResponse response = requestPipeline.execute(request);
          byte[] responseBytes = IOUtils.toByteArray(response.getResponse());
          return FetchedData.fromBytes(responseBytes, mimeType, response.getEncoding(),
              new InputSource(ref.getUri()));
        } catch (GadgetException e) {
          LOG.info("Failed to retrieve: " + ref.toString());
          return null;
        } catch (IOException e) {
          LOG.info("Failed to read: " + ref.toString());
          return null;
        }
      }
     
    };
  }
 
  private UriPolicy makePolicy(Gadget gadget) {
    final Uri gadgetUri = gadget.getContext().getUrl();

    return new UriPolicy() {
      public String rewriteUri(ExternalReference ref, UriEffect effect,
          LoaderType loader, Map<String, ?> hints) {
        URI uri = ref.getUri();
        if (uri.getScheme().equalsIgnoreCase("https") ||
            uri.getScheme().equalsIgnoreCase("http")) {
          return gadgetUri.resolve(Uri.fromJavaUri(uri)).toString();
        }
        return null;
      }
    };
  }

  private void createContainerFor(Document doc, Node el) {
    Element docEl = doc.createElement("html");
    Element head = doc.createElement("head");
    Element body = doc.createElement("body");
    doc.appendChild(docEl);
    docEl.appendChild(head);
    docEl.appendChild(body);
    body.appendChild(el);
  }

  private Element formatErrors(Document doc, InputSource is,
      CharSequence orig, MessageQueue mq, boolean visible) {
    MessageContext mc = new MessageContext();
    Map<InputSource, CharSequence> originalSrc = Maps.newHashMap();
    originalSrc.put(is, orig);
    mc.addInputSource(is);
    SnippetProducer sp = new SnippetProducer(originalSrc, mc);

    Element errElement = doc.createElement("ul");
    // Style defined in gadgets.css
    errElement.setAttribute("class", "gadgets-messages");
    if (!visible) {
      errElement.setAttribute("style", "display: none");
    }
    for (Message msg : mq.getMessages()) {
      // Ignore LINT messages
      if (MessageLevel.LINT.compareTo(msg.getMessageLevel()) <= 0) {
        String snippet = sp.getSnippet(msg);
        String messageText = msg.getMessageLevel().name() + ' ' +
          html(msg.format(mc)) + ':' + snippet;
        Element li = doc.createElement("li");
        li.appendChild(doc.createTextNode(messageText));
        errElement.appendChild(li);
      }
    }
    return errElement;
  }

  private static String html(CharSequence s) {
    StringBuilder sb = new StringBuilder();
    Escaping.escapeXml(s, false, sb);
    return sb.toString();
  }

  private Element tameCajaClientApi(Document doc) {
    Element scriptElement = doc.createElement("script");
    scriptElement.setAttribute("type", "text/javascript");
    scriptElement.appendChild(doc.createTextNode("caja___.enable()"));
    return scriptElement;
  }

  private void logException(Exception cause, MessageQueue mq) {
    StringBuilder errbuilder = new StringBuilder();
    MessageContext mc = new MessageContext();
    if (cause != null) {
      errbuilder.append(cause).append('\n');
    }
    for (Message m : mq.getMessages()) {
      errbuilder.append(m.format(mc)).append('\n');
    }
    LOG.info("Unable to cajole gadget: " + errbuilder);
  }
 
  protected PluginCompiler makePluginCompiler(
      PluginMeta meta, MessageQueue mq) {
    PluginCompiler compiler = new PluginCompiler(
        BuildInfo.getInstance(), meta, mq);
    return compiler;
  }

}
TOP

Related Classes of org.apache.shindig.gadgets.servlet.CajaContentRewriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.