Java tutorial
/* * 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; } }