Package org.apache.shindig.gadgets.rewrite

Source Code of org.apache.shindig.gadgets.rewrite.TemplateRewriter

/*
* 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<String> 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<TemplateProcessor> 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<TemplateProcessor> 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) throws RewritingException {
    Feature f = gadget.getSpec().getModulePrefs().getFeatures()
        .get("opensocial-templates");
    if (f != null && isServerTemplatingEnabled(f)) {
      try {
        rewriteImpl(gadget, f, content);
      } catch (GadgetException ge) {
        throw new RewritingException(ge, ge.getHttpStatusCode());
      }
    }
  }

  /**
   * Disable server-side templating when the feature contains:
   * <pre>
   *   &lt;Param name="disableAutoProcessing"&gt;true&lt;/Param&gt;
   * </pre>
   */
  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<TagRegistry> registries = Lists.newArrayList();
    List<TemplateLibrary> 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<Element> builder = ImmutableList.builder();
    for (int i = 0; i < templateElements.getLength(); i++) {
      builder.add((Element) templateElements.item(i));
    }
    List<Element> 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<Element> allTemplates, List<TemplateLibrary> 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<TagRegistry> registries, List<TemplateLibrary> librariesthrows GadgetException {
    Collection<String> 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<Element> allTemplates) {
    ImmutableSet.Builder<TagHandler> 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<Element> allTemplates, TagRegistry registry) throws GadgetException {
    Map<String, Object> pipelinedData = content.getPipelinedData();

    // If true, client-side processing will be needed
    boolean needsFeature = false;
    List<Element> 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<String> availableData) {
    if ("".equals(requiredData)) {
      return true;
    }
    StringTokenizer st = new StringTokenizer(requiredData, ",");
    while (st.hasMoreTokens()) {
      if (!availableData.contains(st.nextToken().trim())) {
        return false;
      }
    }
    return true;
  }
}
TOP

Related Classes of org.apache.shindig.gadgets.rewrite.TemplateRewriter

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.