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

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

Go to download

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

The 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 com.google.common.base.Strings;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.uri.ConcatUriManager;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * DOM mutator that concatenates resources using the concat servlet
 * @since 2.0.0
 */
public class ConcatVisitor implements DomWalker.Visitor {
  public static class Js extends ConcatVisitor {
    public Js(ContentRewriterFeature.Config config,
              ConcatUriManager uriManager) {
      super(config, uriManager, ConcatUriManager.Type.JS);
    }
  }

  public static class Css extends ConcatVisitor {
    public Css(ContentRewriterFeature.Config config,
               ConcatUriManager uriManager) {
      super(config, uriManager, ConcatUriManager.Type.CSS);
    }
  }

  private final ConcatUriManager uriManager;
  private final ConcatUriManager.Type type;
  private final ContentRewriterFeature.Config config;
  private final boolean split;
  private final boolean singleResourceConcat;

  private ConcatVisitor(ContentRewriterFeature.Config config,
      ConcatUriManager uriManager, ConcatUriManager.Type type) {
    this.uriManager = uriManager;
    this.type = type;
    this.config = config;
    this.split = (type == ConcatUriManager.Type.JS && config.isSplitJsEnabled());
    this.singleResourceConcat = config.isSingleResourceConcatEnabled();
  }

  public VisitStatus visit(Gadget gadget, Node node) throws RewritingException {
    // Reserve JS nodes; always if there's an adjacent rewritable JS node and also when
    // directed to support split-resource concatenation
    if (node.getNodeType() != Node.ELEMENT_NODE ||
        !node.getNodeName().equalsIgnoreCase(type.getTagName())) {
      return VisitStatus.BYPASS;
    }

    Element element = (Element)node;
    if (isRewritableExternData(element)) {
      if (split || singleResourceConcat ||
          isRewritableExternData(getSibling(element, true)) ||
          isRewritableExternData(getSibling(element, false))) {
        return VisitStatus.RESERVE_NODE;
      }
    }

    return VisitStatus.BYPASS;
  }

  /**
   * For css:
   * Link tags are first split into buckets separated by tags with mediaType == "all"
   * / title attribute different from their previous link tag / nodes that are
   * not 'link' tags.
   * This ensures that the buckets can be processed separately without losing title /
   * "all" mediaType information.
   *
   * Link tags with same mediaType are concatenated within each bucket.
   * This exercise ensures that css information is loaded in the same relative order
   * as that of the original html page, and that the css information within
   * mediaType=="all" is retained and applies to all media types.
   *
   * Look at the areLinkNodesBucketable method for details on mediaType=="all" and
   * title attribute
   *
   * Example: Assume we have the following node list. (all have same parent,
   * nodes between Node6 and Node12 are non link nodes, and hence did not come
   * to revisit() call)
   *           -- Node1
   *            -- Node2
   *           -- Node3
   *              -- Node4
   *              -- Node5
   *           -- Node6
   *          -- Node12
   *          -- Node13
   *
   *    First we split to buckets bassed on the adjacency and other conditions.
   *    buckets - [ [ Node1, Node2, Node3 ], [ Node4, Node 5 ], [ Node6 ], [ Node12, Node13 ]
   *    Within each bucket we group them based on media type.
   *    batches - [ Node1, Node2, Node3 ] --> [ [Node1, Node3], [Node2] ]
   *            - [ Node4, Node 5 ] --> [ [ Node4, Node 5 ] ]
   *            - [ Node6 ] --> [ [ Node6 ] ]
   *            - [ Node12, Node13 ] --> [ [ Node12, Node13 ] ]
   *
   * Refer Tests for more examples.
   */
  public boolean revisit(Gadget gadget, List nodes) throws RewritingException {
    // Collate Elements into Buckets.
    List> concatBuckets = Lists.newLinkedList();
    List curBucket = Lists.newLinkedList();
    Iterator nodeIter = nodes.iterator();
    Element cur = (Element)nodeIter.next();
    curBucket.add(cur);
    while (nodeIter.hasNext()) {
      Element next = (Element)nodeIter.next();
      if ((!split && cur != getSibling(next, true)) ||
          (type == ConcatUriManager.Type.CSS && !areLinkNodesBucketable(cur, next))) {
        // Break off current bucket and add to list of all.
        concatBuckets.add(curBucket);
        curBucket = Lists.newLinkedList();
      }
      curBucket.add(next);
      cur = next;
    }

    // Add leftovers.
    concatBuckets.add(curBucket);

    // Split the existing buckets based on media types into concat batches.
    List> concatBatches = Lists.newLinkedList();
    Iterator> batchesIter = concatBuckets.iterator();
    while (batchesIter.hasNext()) {
      splitBatchOnMedia(batchesIter.next(), concatBatches);
    }

    // Prepare batches of Uris to send to generate concat Uris
    List> uriBatches = Lists.newLinkedList();
    batchesIter = concatBatches.iterator();
    while (batchesIter.hasNext()) {
      List batch = batchesIter.next();
      List uris = Lists.newLinkedList();
      if (batch.isEmpty() || !getUris(type, batch, uris)) {
        batchesIter.remove();
        continue;
      }
      uriBatches.add(uris);
    }

    if (uriBatches.isEmpty()) {
      return false;
    }

    // Generate the ConcatUris, then correlate with original elements.
    List concatUris =
        uriManager.make(
          ConcatUriManager.ConcatUri.fromList(gadget, uriBatches, type), !split);

    Iterator> elemBatchIt = concatBatches.iterator();
    Iterator> uriBatchIt = uriBatches.iterator();
    for (ConcatUriManager.ConcatData concatUri : concatUris) {
      List sourceBatch = elemBatchIt.next();
      List sourceUris = uriBatchIt.next();

      // Regardless what happens, inject as many copies of the first node
      // as needed, with new (concat) URI, immediately ahead of the first elem.
      Element firstElem = sourceBatch.get(0);
      for (Uri uri : concatUri.getUris()) {
        Element elemConcat = (Element)firstElem.cloneNode(true);
        elemConcat.setAttribute(type.getSrcAttrib(), uri.toString());
        firstElem.getParentNode().insertBefore(elemConcat, firstElem);
      }

      // Now for all Elements, either A) remove them or B) replace each
      // with a