org.apache.shindig.gadgets.rewrite.ConcatVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shindig-gadgets Show documentation
Show all versions of shindig-gadgets Show documentation
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