/* * Include.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $ * * Copyright (c) 2002, Hewlett-Packard Company and Massachusetts * Institute of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the Hewlett-Packard Company nor the name of the * Massachusetts Institute of Technology nor the names of their * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.app.xmlui.wing; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.cocoon.components.source.SourceUtil; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.transformation.AbstractTransformer; import org.apache.cocoon.xml.dom.DOMStreamer; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.source.impl.validity.NOPValidity; import org.dspace.app.xmlui.wing.element.Body; import org.dspace.app.xmlui.wing.element.Meta; import org.dspace.app.xmlui.wing.element.Options; import org.dspace.app.xmlui.wing.element.PageMeta; import org.dspace.app.xmlui.wing.element.UserMeta; import org.dspace.app.xmlui.wing.element.WingDocument; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /** * The Include class reads an DRI XML file from and merges it into the existing * document stream using the Wing framework. * * If the file is not present at the source provided a blank document is merged, * allowing the pipeline to continue excecution. This is also logged as a warning. * * @author Scott Phillips */ public class Include extends AbstractTransformer implements CacheableProcessingComponent { /** * A data structure describing what elements are to be merged and upon what * key. The first map's key is the name of a mergeable element's name while * the value is a list of all attributes that the element must match on to * be considered the same element. */ private final static Map<String, String[]> mergeableMap; /** Construct the mergeableMap from constant data */ static { Map<String, String[]> buildMap = new HashMap<String, String[]>(); buildMap.put(WingDocument.E_DOCUMENT, null); buildMap.put(Meta.E_META, null); buildMap.put(UserMeta.E_USER_META, null); buildMap.put(PageMeta.E_PAGE_META, null); buildMap.put("artifactmeta", null); buildMap.put("repositoryMeta", null); buildMap.put("community", new String[] { "repositoryIdentifier" }); buildMap.put("collection", new String[] { "repositoryIdentifier" }); buildMap.put(Body.E_BODY, null); buildMap.put(Options.E_OPTIONS, null); buildMap.put(org.dspace.app.xmlui.wing.element.List.E_LIST, new String[] { org.dspace.app.xmlui.wing.element.List.A_NAME }); mergeableMap = buildMap; } /** The source document */ private Document w3cDocument; /** Helper class to stream the w3c DOM into SAX events */ private DOMStreamer streamer; /** Stack of our current location within the document */ private Stack<Element> stack; /** The Cocoon source for the included XML document */ private Source source; /** The src attribute to the cocoon source */ private String src; /** * Read in the given src path into an internal DOM for later processing when * needed. * * @param sourceResolver * Resolver for cocoon pipelines. * @param objectModel * The pipelines's object model. * @param src * The source parameter * @param parameters * The transformer's parameters. */ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters) throws ProcessingException, SAXException, IOException { this.src = src; this.source = resolver.resolveURI(src); } /** * Generate the unique key. * This key must be unique inside the space of this component. * * @return The generated key hashes the src */ public Serializable getKey() { return this.src; } /** * Generate the validity object. * * @return The generated validity object or <code>null</code> if the * component is currently not cacheable. */ public SourceValidity getValidity() { if (source != null) { if (source.exists()) // The file exists so return it's validity. return source.getValidity(); else // The file does not exist so we will just return always valid. This // will have an nastly side effect that if a file is removed from a // running system the cache will remain valid. However if the other // option is to always invalidate the cache if the file is not present // which is not desirable either. return NOPValidity.SHARED_INSTANCE; } else return null; } /** * Receive notification of the beginning of a document. */ public void startDocument() throws SAXException { try { w3cDocument = SourceUtil.toDOM(source); } catch (Exception e) { // since we were unable to parce an XML document from the source given we will // simply log the error as a warning and create a null stack getLogger().warn("File to be included from " + source.toString() +" not found."); stack = null; super.startDocument(); return; } stack = new Stack<Element>(); streamer = new DOMStreamer(contentHandler, lexicalHandler); super.startDocument(); } /** * Receive notification of the end of a document. */ public void endDocument() throws SAXException { stack = null; super.endDocument(); } /** * Receive notification of the beginning of an element. * * @param uri * The Namespace URI, or the empty string if the element has no * Namespace URI or if Namespace processing is not being * performed. * @param localName * The local name (without prefix), or the empty string if * Namespace processing is not being performed. * @param qName * The raw XML 1.0 name (with prefix), or the empty string if raw * names are not available. * @param attributes * The attributes attached to the element. If there are no * attributes, it shall be an empty Attributes object. */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { //getLogger().debug("startElement: " + localName); if(stack == null){ // do nothing fall thru to the call to super.startElement // this means that the document to be read was not parsable // or not found in startDocument() } else if (stack.size() == 0) stack.push(w3cDocument.getDocumentElement()); else { Element peek = stack.peek(); Element foundChild = null; for (Element child : getElementList(peek)) if (isEqual(child, uri, localName, qName, attributes)) foundChild = child; if (foundChild != null) peek.removeChild(foundChild); stack.push(foundChild); } super.startElement(uri, localName, qName, attributes); } /** * Receive notification of the end of an element. * * @param uri * The Namespace URI, or the empty string if the element has no * Namespace URI or if Namespace processing is not being * performed. * @param localName * The local name (without prefix), or the empty string if * Namespace processing is not being performed. * @param qName * The raw XML 1.0 name (with prefix), or the empty string if raw * names are not available. */ public void endElement(String uri, String localName, String qName) throws SAXException { //getLogger().debug("endElement: " + localName); // if the stack is null do nothing fall thru to the call to // super.endElement // this means that the document to be read was not parsable // or not found in startDocument() if(stack!=null){ Element poped = stack.pop(); if (poped != null) { //getLogger().debug("startElement: streaming"); for (Node node : getNodeList(poped)) streamer.stream(node); } } super.endElement(uri, localName, qName); } /** * Receive notification of character data. * * @param c * The characters from the XML document. * @param start * The start position in the array. * @param len * The number of characters to read from the array. */ public void characters(char c[], int start, int len) throws SAXException { super.characters(c, start, len); } /** * Determine if the given SAX event is the same as the given w3c DOM * element. If so then return true, otherwise false. * * @param child * W3C DOM element to compare with the SAX event. * @param uri * The namespace URI of the SAX event. * @param localName * The localName of the SAX event. * @param qName * The qualified name of the SAX event. * @param attributes * The attributes of the SAX event. * @return if equal. */ private boolean isEqual(Element child, String uri, String localName, String qName, Attributes attributes) { if (child == null) return false; if (uri != null && !uri.equals(child.getNamespaceURI())) return false; if (localName != null && !localName.equals(child.getLocalName())) return false; if (!mergeableMap.containsKey(localName)) return false; String[] attributeIdentities = mergeableMap.get(localName); if (attributeIdentities != null) { for (String attributeIdentity : attributeIdentities) { String testIdentity = attributes.getValue(attributeIdentity); String childIdentity = child.getAttribute(attributeIdentity); if (childIdentity != null && childIdentity.equals(testIdentity)) continue; if (childIdentity == null && testIdentity == null) continue; return false; } } return true; } /** * DOM Helper method - Get a list of all child elements. * * @param element * The parent element * @return a list of all child elements. */ private static List<Element> getElementList(Element element) { if (element == null) return new ArrayList<Element>(); NodeList nodeList = element.getChildNodes(); List<Element> resultList = new ArrayList<Element>(); for (int i = 0; i < nodeList.getLength(); i++) { if (nodeList.item(i).getNodeType() == Node.ELEMENT_NODE) resultList.add((Element) nodeList.item(i)); } return resultList; } /** * DOM Helper method - Get a list of all child nodes. * * @param element * The parent element * @return a list of all child nodes. */ private static List<Node> getNodeList(Element element) { if (element == null) return new ArrayList<Node>(); NodeList nodeList = element.getChildNodes(); List<Node> resultList = new ArrayList<Node>(); for (int i = 0; i < nodeList.getLength(); i++) { resultList.add((Node) nodeList.item(i)); } return resultList; } /** * Recycle */ public void recycle() { this.w3cDocument = null; this.streamer = null; this.stack = null; this.source = null; super.recycle(); } }