/* * 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.shindig.gadgets.GadgetException; import org.apache.shindig.gadgets.http.HttpResponse; import org.apache.shindig.gadgets.parse.GadgetHtmlParser; import org.apache.shindig.gadgets.parse.HtmlSerializer; import org.w3c.dom.Document; /** * Object that maintains a String representation of arbitrary contents * and a consistent view of those contents as an HTML parse tree. */ public class MutableContent { private String content; private HttpResponse contentSource; private Document document; private final GadgetHtmlParser contentParser; private static final String MUTABLE_CONTENT_LISTENER = "MutableContentListener"; public static void notifyEdit(Document doc) { MutableContent mc = (MutableContent) doc.getUserData(MUTABLE_CONTENT_LISTENER); if (mc != null) { mc.documentChanged(); } } /** * Construct with decoded string content */ public MutableContent(GadgetHtmlParser contentParser, String content) { this.contentParser = contentParser; this.content = content; } /** * Construct with HttpResponse so we can defer string decoding until we actually need * the content. Given that we dont rewrite many mime types this is a performance advantage */ public MutableContent(GadgetHtmlParser contentParser, HttpResponse contentSource) { this.contentParser = contentParser; this.contentSource = contentSource; } /** * Retrieves the current content for this object in String form. * If content has been retrieved in parse tree form and has * been edited, the String form is computed from the parse tree by * rendering it. It is <b>strongly</b> encouraged to avoid switching * between retrieval of parse tree (through {@code getParseTree}), * with subsequent edits and retrieval of String contents to avoid * repeated serialization and deserialization. * @return Renderable/active content. */ public String getContent() { if (content == null) { if (contentSource != null) { content = contentSource.getResponseAsString(); // Clear on first use contentSource = null; } else if (document != null) { content = HtmlSerializer.serialize(document); } } return content; } /** * Sets the object's content as a raw String. Note, this operation * may clears the document if the content has changed * @param newContent New content. */ public void setContent(String newContent) { // TODO - Equality check may be unnecessary overhead if (content == null || !content.equals(newContent)) { content = newContent; document = null; contentSource = null; } } /** * Notification that the content of the document has changed. Causes the content * string to be cleared */ public void documentChanged() { if (document != null) { content = null; contentSource = null; } } /** * Retrieves the object contents in parsed form, if a * {@code GadgetHtmlParser} is configured and is able to parse the string * contents appropriately. To modify the object's * contents by parse tree after setting new String contents, * this method must be called again. However, this practice is highly * discouraged, as parsing a tree from String is a costly operation and should * be done at most once per rewrite. */ public Document getDocument() { // TODO - Consider actually imposing one parse limit on rewriter pipeline if (document != null) { return document; } try { document = contentParser.parseDom(getContent()); document.setUserData(MUTABLE_CONTENT_LISTENER, this, null); } catch (GadgetException e) { // TODO: emit info message return null; } return document; } /** * True if current state has a parsed document. Allows rewriters to switch mode based on * which content is most readily available */ public boolean hasDocument() { return (document != null); } }