Java tutorial
package com.dotmarketing.viewtools; /* * 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. */ import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.velocity.tools.generic.ValueParser; import org.apache.velocity.tools.view.tools.ViewTool; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import com.dotmarketing.util.Logger; import com.dotmarketing.viewtools.cache.XmlToolCache; import com.dotmarketing.viewtools.util.ConversionUtils; import com.dotmarketing.viewtools.bean.XmlToolDoc; /** * <p> * Tool for reading/navigating XML files. This uses dom4j under the covers to provide complete XPath support for * traversing XML files. * </p> * <p> * Here's a short example: * * <pre> * XML file: * <foo><bar>woogie</bar><a name="test"/></foo> * * Template: * $foo.bar.text * $foo.find('a') * $foo.a.name * * Output: * woogie * <a name="test"/> * test * * Configuration: * <tools> * <toolbox scope="application"> * <tool class="org.apache.velocity.tools.generic.XmlTool" * key="foo" file="doc.xml"/> * </toolbox> * </tools> * </pre> * * </p> * <p> * Note that this tool is included in the default GenericTools configuration under the key "xml", but unless you set * safeMode="false" for it, you will only be able to parse XML strings. Safe mode is on by default and blocks access to * the {@link #read(Object)} method. * </p> * * @author Nathan Bubna * @version $Revision$ $Date: 2006-11-27 10:49:37 -0800 (Mon, 27 Nov 2006) $ * @since VelocityTools 2.0 */ public class XmlTool implements ViewTool { public static final String FILE_KEY = "file"; private List<Node> nodes; private static long ttl = 30; public XmlTool() { } public XmlTool(Node node) { this(Collections.singletonList(node)); } public XmlTool(List<Node> nodes) { this.nodes = nodes; } /** * Looks for the "file" parameter and automatically uses {@link #read(String)} to parse the file and set the * resulting {@link Document} as the root node for this instance. */ protected void configure(ValueParser parser) { String file = parser.getString(FILE_KEY); if (file != null) { try { read(file); } catch (IllegalArgumentException iae) { throw iae; } catch (Exception e) { throw new RuntimeException("Could not read XML file at: " + file, e); } } } /** * Sets a singular root {@link Node} for this instance. */ protected void setRoot(Node node) { if (node instanceof Document) { node = ((Document) node).getRootElement(); } this.nodes = new ArrayList<Node>(1); this.nodes.add(node); } /** * Creates a {@link URL} from the string and passes it to {@link #read(URL)}. */ protected void read(String file) throws Exception { URL url = ConversionUtils.toURL(file, this); if (url == null) { throw new IllegalArgumentException( "Could not find file, classpath resource or standard URL for '" + file + "'."); } read(url); } /** * Reads, parses and creates a {@link Document} from the given {@link URL} and uses it as the root {@link Node} for * this instance. */ protected void read(URL url) throws Exception { SAXReader reader = new SAXReader(); setRoot(reader.read(url)); } /** * Parses the given XML string and uses the resulting {@link Document} as the root {@link Node}. */ protected void parse(String xml) throws Exception { setRoot(DocumentHelper.parseText(xml)); } /** * Set the cache Time To Live * * @param ttl * time in minutes * @author Oswaldo Gallango */ public static void setTTL(long ttl_time) { ttl = ttl_time; } /** * Return the XmlTool Timte to Live in minutes * * @return ttl * @author Oswaldo Gallango */ public static long getTTL() { return ttl; } /** * If safe mode is explicitly turned off for this tool, then this will accept either a {@link URL} or the string * representation thereof. If valid, it will return a new {@link XmlTool} instance with that document as the root * {@link Node}. If reading the URL or parsing its content fails or if safe mode is on (the default), this will * return {@code null}. This methos use cache. if the TTL is not modifyed by dedefault the cache is refresh every * 30 minutes * * @throws Exception */ public XmlTool read(Object o) throws Exception { if (o == null) { return null; } XmlTool xml = new XmlTool(); XmlToolDoc doc = XmlToolCache.getXmlToolDoc(String.valueOf(o)); if (doc == null) { String xmlPath = ""; if (o instanceof URL) { xml.read((URL) o); } else { String file = String.valueOf(o); xml.read(file); } doc = new XmlToolDoc(); doc.setXmlPath(String.valueOf(o)); doc.setXmlTool(xml); doc.setTtl(new Date().getTime() + (ttl * 60000)); XmlToolCache.addXmlToolDoc(doc); } else { xml = doc.getXmlTool(); } return xml; } /** * This accepts XML in form. If the XML is valid, it will return a new {@link XmlTool} instance with the resulting * XML document as the root {@link Node}. If parsing the content fails, this will return {@code null}. * * @throws Exception */ public XmlTool parse(Object o) throws Exception { if (o == null) { return null; } String s = String.valueOf(o); XmlTool xml = new XmlTool(); xml.parse(s); return xml; } /** * This will first attempt to find an attribute with the specified name and return its value. If no such attribute * exists or its value is {@code null}, this will attempt to convert the given value to a {@link Number} and get * the result of {@link #get(Number)}. If the number conversion fails, then this will convert the object to a * string. If that string does not contain a '/', it appends the result of {@link #getPath()} and a '/' to the front * of it. Finally, it delegates the string to the {@link #find(String)} method and returns the result of that. */ public Object get(Object o) { if (isEmpty() || o == null) { return null; } String attr = attr(o); if (attr != null) { return attr; } Number i = ConversionUtils.toNumber(o); if (i != null) { return get(i); } String s = String.valueOf(o); if (s.length() == 0) { return null; } if (s.indexOf('/') < 0) { s = getPath() + '/' + s; } return find(s); } /** * Asks {@link #get(Object)} for a "name" result. If none, this will return the result of {@link #getNodeName()}. */ public Object getName() { // give attributes and child elements priority Object name = get("name"); if (name != null) { return name; } return getNodeName(); } /** * Returns the name of the root node. If the internal {@link Node} list has more than one {@link Node}, it will * only return the name of the first node in the list. */ public String getNodeName() { if (isEmpty()) { return null; } return node().getName(); } /** * Returns the XPath that identifies the first/sole {@link Node} represented by this instance. */ public String getPath() { if (isEmpty()) { return null; } return node().getPath(); } /** * Returns the value of the specified attribute for the first/sole {@link Node} in the internal Node list for this * instance, if that Node is an {@link Element}. If it is a non-Element node type or there is no value for that * attribute in this element, then this will return {@code null}. */ public String attr(Object o) { if (o == null) { return null; } String key = String.valueOf(o); Node node = node(); if (node instanceof Element) { return ((Element) node).attributeValue(key); } return null; } /** * Returns a {@link Map} of all attributes for the first/sole {@link Node} held internally by this instance. If that * Node is not an {@link Element}, this will return null. */ public Map<String, String> attributes() { Node node = node(); if (node instanceof Element) { Map<String, String> attrs = new HashMap<String, String>(); for (Iterator i = ((Element) node).attributeIterator(); i.hasNext();) { Attribute a = (Attribute) i.next(); attrs.put(a.getName(), a.getValue()); } return attrs; } return null; } /** * Returns {@code true} if there are no {@link Node}s internally held by this instance. */ public boolean isEmpty() { return (nodes == null || nodes.isEmpty()); } /** * Returns the number of {@link Node}s internally held by this instance. */ public int size() { if (isEmpty()) { return 0; } return nodes.size(); } /** * Returns an {@link Iterator} that returns new {@link XmlTool} instances for each {@link Node} held internally by * this instance. */ public Iterator<XmlTool> iterator() { if (isEmpty()) { return null; } return new NodeIterator(nodes.iterator()); } /** * Returns an {@link XmlTool} that wraps only the first {@link Node} from this instance's internal Node list. */ public XmlTool getFirst() { if (size() == 1) { return this; } return new XmlTool(node()); } /** * Returns an {@link XmlTool} that wraps only the last {@link Node} from this instance's internal Node list. */ public XmlTool getLast() { if (size() == 1) { return this; } return new XmlTool(nodes.get(size() - 1)); } /** * Returns an {@link XmlTool} that wraps the specified {@link Node} from this instance's internal Node list. */ public XmlTool get(Number n) { if (n == null) { return null; } int i = n.intValue(); if (i < 0 || i > size() - 1) { return null; } return new XmlTool(nodes.get(i)); } /** * Returns the first/sole {@link Node} from this instance's internal Node list, if any. */ public Node node() { if (isEmpty()) { return null; } return nodes.get(0); } /** * Converts the specified object to a String and calls {@link #find(String)} with that. */ public XmlTool find(Object o) { if (o == null || isEmpty()) { return null; } return find(String.valueOf(o)); } /** * Performs an XPath selection on the current set of {@link Node}s held by this instance and returns a new * {@link XmlTool} instance that wraps those results. If the specified value is null or this instance does not * currently hold any nodes, then this will return {@code null}. If the specified value, when converted to a * string, does not contain a '/' character, then it has "//" prepended to it. This means that a call to * {@code $xml.find("a")} is equivalent to calling {@code $xml.find("//a")}. The full range of XPath selectors is * supported here. */ public XmlTool find(String xpath) { if (xpath == null || xpath.length() == 0) { return null; } if (xpath.indexOf('/') < 0) { xpath = "//" + xpath; } List<Node> found = new ArrayList<Node>(); for (Node n : nodes) { found.addAll((List<Node>) n.selectNodes(xpath)); } if (found.isEmpty()) { return null; } return new XmlTool(found); } /** * Returns a new {@link XmlTool} instance that wraps the parent {@link Element} of the first/sole {@link Node} being * wrapped by this instance. */ public XmlTool getParent() { if (isEmpty()) { return null; } Element parent = node().getParent(); if (parent == null) { return null; } return new XmlTool(parent); } /** * Returns a new {@link XmlTool} instance that wraps the parent {@link Element}s of each of the {@link Node}s * being wrapped by this instance. This does not return all ancestors, just the immediate parents. */ public XmlTool parents() { if (isEmpty()) { return null; } if (size() == 1) { return getParent(); } List<Node> parents = new ArrayList<Node>(size()); for (Node n : nodes) { Element parent = n.getParent(); if (parent != null && !parents.contains(parent)) { parents.add(parent); } } if (parents.isEmpty()) { return null; } return new XmlTool(parents); } /** * Returns a new {@link XmlTool} instance that wraps all the child {@link Element}s of all the current internally * held nodes that are {@link Element}s themselves. */ public XmlTool children() { if (isEmpty()) { return null; } List<Node> kids = new ArrayList<Node>(); for (Node n : nodes) { if (n instanceof Element) { kids.addAll((List<Node>) ((Element) n).elements()); } } return new XmlTool(kids); } /** * Returns the concatenated text content of all the internally held nodes. Obviously, this is most useful when only * one node is held. */ public String getText() { if (isEmpty()) { return null; } StringBuilder out = new StringBuilder(); for (Node n : nodes) { String text = n.getText(); if (text != null) { out.append(text); } } String result = out.toString().trim(); if (result.length() > 0) { return result; } return null; } /** * If this instance has no XML {@link Node}s, then this returns the result of {@code super.toString()}. Otherwise, * it returns the XML (as a string) of all the internally held nodes that are not {@link Attribute}s. For * attributes, only the value is used. */ public String toString() { if (isEmpty()) { return super.toString(); } StringBuilder out = new StringBuilder(); for (Node n : nodes) { if (n instanceof Attribute) { out.append(n.getText().trim()); } else { out.append(n.asXML()); } } return out.toString(); } /** * Iterator implementation that wraps a Node list iterator to return new XmlTool instances for each item in the * wrapped iterator.s */ public static class NodeIterator implements Iterator<XmlTool> { private Iterator<Node> i; public NodeIterator(Iterator<Node> i) { this.i = i; } public boolean hasNext() { return i.hasNext(); } public XmlTool next() { return new XmlTool(i.next()); } public void remove() { i.remove(); } } public void init(Object arg0) { } }