All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.shindig.gadgets.rewrite.TemplateRewriter Maven / Gradle / Ivy

Go to download

Renders gadgets, provides the gadget metadata service, and serves all javascript required by the OpenSocial specification.

There is a newer version: 3.0.0-beta4
Show newest version
/*
 * 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.rewrite;

import org.apache.commons.lang.StringUtils;
import org.apache.shindig.common.JsonSerializer;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.xml.DomUtil;
import org.apache.shindig.expressions.Expressions;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.MessageBundleFactory;
import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
import org.apache.shindig.gadgets.render.SanitizingGadgetRewriter;
import org.apache.shindig.gadgets.spec.Feature;
import org.apache.shindig.gadgets.spec.MessageBundle;
import org.apache.shindig.gadgets.templates.ContainerTagLibraryFactory;
import org.apache.shindig.gadgets.templates.MessageELResolver;
import org.apache.shindig.gadgets.templates.TagRegistry;
import org.apache.shindig.gadgets.templates.TemplateContext;
import org.apache.shindig.gadgets.templates.TemplateLibrary;
import org.apache.shindig.gadgets.templates.TemplateLibraryFactory;
import org.apache.shindig.gadgets.templates.TemplateParserException;
import org.apache.shindig.gadgets.templates.TemplateProcessor;
import org.apache.shindig.gadgets.templates.TemplateResource;
import org.apache.shindig.gadgets.templates.tags.CompositeTagRegistry;
import org.apache.shindig.gadgets.templates.tags.DefaultTagRegistry;
import org.apache.shindig.gadgets.templates.tags.TagHandler;
import org.apache.shindig.gadgets.templates.tags.TemplateBasedTagHandler;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This ContentRewriter uses a TemplateProcessor to replace os-template
 * tag contents of a gadget spec with their rendered equivalents.
 *
 * Only templates without the @name and @tag attributes are processed
 * automatically.
 */
public class TemplateRewriter implements GadgetRewriter {

  public final static Set TAGS = ImmutableSet.of("script");

  /** Set to true to block auto-processing of templates */
  static final String DISABLE_AUTO_PROCESSING_PARAM = "disableAutoProcessing";
  
  /** Specifies what template libraries to load */
  static final String REQUIRE_LIBRARY_PARAM = "requireLibrary";
  
  /** Enable client support? **/
  static final String CLIENT_SUPPORT_PARAM = "client";  

  private static final Logger logger = Logger.getLogger(TemplateRewriter.class.getName());
  
  /**
   * Provider of the processor.  TemplateRewriters are stateless and multithreaded,
   * processors are not.
   */
  private final Provider processor;
  private final MessageBundleFactory messageBundleFactory;
  private final Expressions expressions;
  private final TagRegistry baseTagRegistry;
  private final TemplateLibraryFactory libraryFactory;
  private final ContainerTagLibraryFactory containerTags;

  @Inject
  public TemplateRewriter(Provider processor,
      MessageBundleFactory messageBundleFactory, Expressions expressions, 
      TagRegistry baseTagRegistry, TemplateLibraryFactory libraryFactory,
      ContainerTagLibraryFactory containerTags) {
    this.processor = processor;
    this.messageBundleFactory = messageBundleFactory;
    this.expressions = expressions;
    this.baseTagRegistry = baseTagRegistry;
    this.libraryFactory = libraryFactory;
    this.containerTags = containerTags;
  }

  public void rewrite(Gadget gadget, MutableContent content) {
    Feature f = gadget.getSpec().getModulePrefs().getFeatures()
        .get("opensocial-templates");
    if (f != null && isServerTemplatingEnabled(f)) {
      try {
        rewriteImpl(gadget, f, content);
      } catch (GadgetException ge) {
        // TODO: Rewriter interface needs to be modified to handle GadgetException or
        // RewriterException or something along those lines.
        throw new RuntimeException(ge);
      }
    }
  }

  /**
   * Disable server-side templating when the feature contains:
   * 
   *   <Param name="disableAutoProcessing">true</Param>
   * 
*/ private boolean isServerTemplatingEnabled(Feature f) { return (!"true".equalsIgnoreCase(f.getParam(DISABLE_AUTO_PROCESSING_PARAM))); } private void rewriteImpl(Gadget gadget, Feature f, MutableContent content) throws GadgetException { List registries = Lists.newArrayList(); List libraries = Lists.newArrayList(); // TODO: Add View-specific library as Priority 0 // Built-in Java-based tags - Priority 1 registries.add(baseTagRegistry); TemplateLibrary osmlLibrary = containerTags.getLibrary(gadget.getContext().getContainer()); // OSML Built-in tags - Priority 2 registries.add(osmlLibrary.getTagRegistry()); libraries.add(osmlLibrary); NodeList templateElements = content.getDocument() .getElementsByTagName(GadgetHtmlParser.OSML_TEMPLATE_TAG); ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < templateElements.getLength(); i++) { builder.add((Element) templateElements.item(i)); } List templates = builder.build(); // User-defined custom tags - Priority 3 registries.add(registerCustomTags(templates)); // User-defined libraries - Priority 4 loadTemplateLibraries(gadget.getContext(), f, registries, libraries); TagRegistry registry = new CompositeTagRegistry(registries); TemplateContext templateContext = new TemplateContext(gadget, content.getPipelinedData()); boolean needsFeature = executeTemplates(templateContext, content, templates, registry); // Check if a feature param overrides our guess at whether the client-side // feature is needed. String clientOverride = f.getParam(CLIENT_SUPPORT_PARAM); if ("true".equalsIgnoreCase(clientOverride)) { needsFeature = true; } else if ("false".equalsIgnoreCase(clientOverride)) { needsFeature = false; } Element head = (Element) DomUtil.getFirstNamedChildNode( content.getDocument().getDocumentElement(), "head"); postProcess(templateContext, needsFeature, head, templates, libraries); } /** * Post-processes the gadget content after rendering templates. * * @param templateContext TemplateContext to operate on * @param needsFeature Should the templates feature be made available to * client? * @param head Head element of the gadget's document * @param libraries Keeps track of all libraries, and which got used * @param allTemplates A list of all the template nodes * @param libraries A list of all registered libraries */ private void postProcess(TemplateContext templateContext, boolean needsFeature, Element head, List allTemplates, List libraries) { // Inject all the needed library assets. // TODO: inject library assets that aren't used on the server, but will // be needed on the client for (TemplateResource resource : templateContext.getResources()) { injectTemplateLibraryAssets(resource, head); } // If we don't need the feature, remove it and all templates from the gadget if (!needsFeature) { templateContext.getGadget().removeFeature("opensocial-templates"); for (Element template : allTemplates) { Node parent = template.getParentNode(); if (parent != null) { parent.removeChild(template); } } } else { // If the feature is to be kept, inject the libraries. // Library assets will be generated on the client. // TODO: only inject the templates, not the full scripts/styles for (TemplateLibrary library : libraries) { injectTemplateLibrary(library, head); } } } private void loadTemplateLibraries(GadgetContext context, Feature f, List registries, List libraries) throws GadgetException { Collection urls = f.getParams().get(REQUIRE_LIBRARY_PARAM); if (urls != null) { for (String url : urls) { Uri uri = Uri.parse(url.trim()); uri = context.getUrl().resolve(uri); try { TemplateLibrary library = libraryFactory.loadTemplateLibrary(context, uri); registries.add(library.getTagRegistry()); libraries.add(library); } catch (TemplateParserException te) { // Suppress exceptions due to malformed template libraries logger.log(Level.WARNING, null, te); } } } } private void injectTemplateLibraryAssets(TemplateResource resource, Element head) { Element contentElement; switch (resource.getType()) { case JAVASCRIPT: contentElement = head.getOwnerDocument().createElement("script"); contentElement.setAttribute("type", "text/javascript"); break; case STYLE: contentElement = head.getOwnerDocument().createElement("style"); contentElement.setAttribute("type", "text/css"); break; default: throw new IllegalStateException("Unhandled type"); } if (resource.isSafe()) { SanitizingGadgetRewriter.bypassSanitization(contentElement, false); } contentElement.setTextContent(resource.getContent()); head.appendChild(contentElement); } private void injectTemplateLibrary(TemplateLibrary library, Element head) { try { String libraryContent = library.serialize(); if (StringUtils.isEmpty(libraryContent)) { return; } Element scriptElement = head.getOwnerDocument().createElement("script"); scriptElement.setAttribute("type", "text/javascript"); StringBuilder buffer = new StringBuilder(); buffer.append("opensocial.template.Loader.loadContent("); JsonSerializer.appendString(buffer, library.serialize()); buffer.append(","); JsonSerializer.appendString(buffer, library.getLibraryUri().toString()); buffer.append(");"); scriptElement.setTextContent(buffer.toString()); head.appendChild(scriptElement); } catch (IOException ioe) { // This should never happen. } } /** * Register templates with a "tag" attribute. */ private TagRegistry registerCustomTags(List allTemplates) { ImmutableSet.Builder handlers = ImmutableSet.builder(); for (Element template : allTemplates) { // Only process templates with a tag attribute if (template.getAttribute("tag").length() == 0) { continue; } // TODO: split() is a regex compile, and should be avoided String [] nameParts = template.getAttribute("tag").split(":"); // At this time, we only support if (nameParts.length != 2) { continue; } String namespaceUri = template.lookupNamespaceURI(nameParts[0]); if (namespaceUri != null) { handlers.add(new TemplateBasedTagHandler(template, namespaceUri, nameParts[1])); } } return new DefaultTagRegistry(handlers.build()); } /** * Processes and renders inline templates. * @return Do we think the templates feature is still needed on the client? */ private boolean executeTemplates(TemplateContext templateContext, MutableContent content, List allTemplates, TagRegistry registry) throws GadgetException { Map pipelinedData = content.getPipelinedData(); // If true, client-side processing will be needed boolean needsFeature = false; List templates = Lists.newArrayList(); for (Element element : allTemplates) { String tag = element.getAttribute("tag"); String require = element.getAttribute("require"); if (!checkRequiredData(require, pipelinedData.keySet())) { // Can't be processed on the server at all; keep client-side processing needsFeature = true; } else if ("".equals(tag)) { templates.add(element); } } if (!templates.isEmpty()) { Gadget gadget = templateContext.getGadget(); MessageBundle bundle = messageBundleFactory.getBundle(gadget.getSpec(), gadget.getContext().getLocale(), gadget.getContext().getIgnoreCache()); MessageELResolver messageELResolver = new MessageELResolver(expressions, bundle); int autoUpdateID = 0; for (Element template : templates) { DocumentFragment result = processor.get().processTemplate( template, templateContext, messageELResolver, registry); // TODO: sanitized renders should ignore this value if ("true".equals(template.getAttribute("autoUpdate"))) { // autoUpdate requires client-side processing. needsFeature = true; Element span = template.getOwnerDocument().createElement("span"); String id = "template_auto" + (autoUpdateID++); span.setAttribute("id", "_T_" + id); template.setAttribute("name", id); template.getParentNode().insertBefore(span, template); span.appendChild(result); } else { template.getParentNode().insertBefore(result, template); template.getParentNode().removeChild(template); } } MutableContent.notifyEdit(content.getDocument()); } return needsFeature; } /** * Checks that all the required data is available at rewriting time. * @param requiredData A string of comma-separated data set names * @param availableData A map of available data sets * @return true if all required data sets are present, false otherwise */ private static boolean checkRequiredData(String requiredData, Set availableData) { if ("".equals(requiredData)) { return true; } StringTokenizer st = new StringTokenizer(requiredData, ","); while (st.hasMoreTokens()) { if (!availableData.contains(st.nextToken().trim())) { return false; } } return true; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy