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

org.apache.shindig.gadgets.rewrite.DomWalker 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.collect.Lists;
import com.google.common.collect.Maps;

import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
import org.apache.shindig.gadgets.spec.GadgetSpec;
import org.apache.shindig.gadgets.uri.UriCommon.Param;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Framework-in-a-framework facilitating the common Visitor case
 * in which a DOM tree is walked in order to manipulate it.
 *
 * See subclass doc for additional detail.
 *
 * @since 2.0.0
 */
public final class DomWalker {
  private DomWalker() {}

  /**
   * Implemented by classes that do actual manipulation of the DOM
   * while {@code DomWalker.ContentVisitor} walks it. {@code Visitor}
   * instances are called for each {@code Node} in the DOM in the order
   * they are registered with the {@code DomVisitor.ContentVisitor}
   */
  public interface Visitor {
    /**
     * Returned by the {@code visit(Gadget, Node)} method, signaling:
     * 
     * BYPASS = Visitor doesn't care about the node.
     * MODIFY = Visitor has modified the node.
     * RESERVE_NODE = Visitor reserves exactly the node passed. No other
     *   Visitor will visit the node.
     * RESERVE_TREE = Visitor reserves the node passed and all its descendants
     *   No other Visitor will visit them.
     *   
     * Visitors are expected to be well-behaved in that they do not
     * modify unreserved nodes: that is, in revisit(...) they do not access
     * adjacent, parent, etc. nodes and modify them. visit(...) may return
     * MODIFY to indicate a modification of the given node.
     * 
     * Other append and delete operations are acceptable
     * but only in revisit(). Reservations are supported in order to support
     * "batched" lookups relating to a similar set of data retrieved from a
     * backend.
     */
    public enum VisitStatus {
      BYPASS,
      MODIFY,
      RESERVE_NODE,
      RESERVE_TREE
    }
    
    /**
     * Visit a particular Node in the DOM.
     * 
     * @param gadget Context for the request.
     * @param node Node being visited.
     * @return Status, see {@code VisitStatus}
     */
    VisitStatus visit(Gadget gadget, Node node) throws RewritingException;
    
    /**
     * Revisit a node in the DOM that was marked by the
     * {@code visit(Gadget, Node)} as reserved during DOM traversal.
     * 
     * @param gadget Context for the request.
     * @param nodes Nodes being revisited, previously marked as reserved.
     * @return True if any node modified, false otherwise.
     */
    boolean revisit(Gadget gadget, List nodes) throws RewritingException;
  }
  
  /**
   * Rewriter that traverses the DOM, passing each node to its
   * list of {@code Visitor} instances in order. Each visitor
   * may bypass, modify, or reserve the node. Reserved nodes
   * will be revisited after the entire DOM tree is walked.
   * The DOM tree is walked in depth-first order.
   */
  public static class Rewriter implements GadgetRewriter, ResponseRewriter {
    private final List visitors;
    
    public Rewriter(List visitors) {
      this.visitors = visitors;
    }
    
    public Rewriter(Visitor... visitors) {
      this.visitors = Arrays.asList(visitors);
    }
    
    public Rewriter() {
      this.visitors = null;
    }

    // Override this to supply a list of Visitors generated using request context
    // rather than supplied at construction time.
    protected List makeVisitors(Gadget context, Uri gadgetUri) {
      return visitors;
    }

    /**
     * Performs the DomWalker rewrite operation described in class javadoc.
     */
    public void rewrite(Gadget gadget, MutableContent content)
        throws RewritingException {
      rewrite(makeVisitors(gadget, gadget.getSpec().getUrl()), gadget, content);
    }

    public void rewrite(HttpRequest request, HttpResponseBuilder builder)
        throws RewritingException {
      if (RewriterUtils.isHtml(request, builder)) {
        Gadget context = makeGadget(request);
        rewrite(makeVisitors(context, request.getGadget()), context, builder);
      }
    }
    
    private boolean rewrite(List visitors, Gadget gadget, MutableContent content) 
        throws RewritingException {
      Map> reservations = Maps.newHashMap();
        
      LinkedList toVisit = Lists.newLinkedList();
      Document doc = content.getDocument();
      if (doc == null) {
        throw new RewritingException("content.getDocument is null. Content: "
                                     + content.getContent(),
                                     HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      }
      toVisit.add(doc.getDocumentElement());
      boolean mutated = false;
      while (!toVisit.isEmpty()) {
        Node visiting = toVisit.removeFirst();
          
        // Iterate through all visitors evaluating their visitation status.
        boolean treeReserved = false;
        boolean nodeReserved = false;
        for (Visitor visitor : visitors) {
          switch(visitor.visit(gadget, visiting)) {
          case MODIFY:
            content.documentChanged();
            mutated = true;
            break;
          case RESERVE_NODE:
            nodeReserved = true;
            break;
          case RESERVE_TREE:
            treeReserved = true;
            break;
          default:
            // Aka BYPASS - do nothing.
            break;
          }
            
          if (nodeReserved || treeReserved) {
            // Reservation was made.
            if (!reservations.containsKey(visitor)) {
              reservations.put(visitor, Lists.newLinkedList());
            }
            reservations.get(visitor).add(visiting);
            break;
          }
        }
          
        if (!treeReserved && visiting.hasChildNodes()) {
          // Tree wasn't reserved - walk children.
          // In order to preserve DFS order, walk children in reverse.
          for (Node child = visiting.getLastChild(); child != null;
               child = child.getPreviousSibling()) {
            toVisit.addFirst(child);
          }
        }
      }
        
      // Run through all reservations, revisiting as needed.
      for (Visitor visitor : visitors) {
        List nodesReserved = reservations.get(visitor);
        if (nodesReserved != null && visitor.revisit(gadget, nodesReserved)) {
          content.documentChanged();
          mutated = true;
        }
      }
      
      return mutated;
    }
  }

  // TODO: Remove these lame hacks by changing Gadget to a proper general Context object.
  public static Gadget makeGadget(GadgetContext context) {
    try {
      final GadgetSpec spec = new GadgetSpec(context.getUrl(),
          "" +
          "");
      return new Gadget().setSpec(spec).setContext(context);
    } catch (Exception e) {
      throw new RuntimeException("Unexpected boilerplate parse failure");
    }
  }

  public static Gadget makeGadget(final HttpRequest request) {
    return makeGadget(new GadgetContext() {
      @Override
      public Uri getUrl() {
        return request.getUri();
      }

      @Override
      public String getParameter(String key) {
        return request.getParam(key);
      }

      @Override
      public boolean getIgnoreCache() {
        return request.getIgnoreCache();
      }

      @Override
      public String getContainer() {
        return request.getContainer();
      }

      @Override
      public boolean getDebug() {
        return "1".equalsIgnoreCase(getParameter(Param.DEBUG.getKey()));
      }
    });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy