org.apache.shindig.gadgets.servlet.CajaContentRewriter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.shindig.gadgets.servlet.CajaContentRewriter.java

Source

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

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

                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("");
                }
            }
        }
    }

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

}