/** * Copyright (C) 2010 Orbeon, Inc. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.processor.generator; import org.orbeon.dom.Document; import org.orbeon.dom.DocumentFactory; import org.orbeon.oxf.cache.OutputCacheKey; import org.orbeon.oxf.cache.SimpleOutputCacheKey; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.processor.ProcessorImpl; import org.orbeon.oxf.processor.ProcessorInputOutputInfo; import org.orbeon.oxf.processor.ProcessorOutput; import org.orbeon.oxf.processor.impl.CacheableTransformerOutputImpl; import org.orbeon.oxf.util.XPath; import org.orbeon.oxf.xml.TransformerUtils; import org.orbeon.oxf.xml.XMLReceiver; import org.orbeon.oxf.xml.dom4j.Dom4jUtils; import org.orbeon.saxon.om.DocumentInfo; import org.orbeon.saxon.om.NodeInfo; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; /** * DOM Document to sax event processor. * * A note wrt caching. Each instance of DOMGenerator creates a unique key for caching. The * validity, however, is provided by whoever instantiates the DOMGenerator. The intent is that * a.) we don't get cache hits for two different DOM objects that happen to have equivalent content. * ( source of performance problem in past ), and * b.) two doc fragments are considered equivalent input iff they are the same fragment. * * Unfortunately at the moment (b) happens more by coincidence than by design. That is just so * happens that that with current impl and usage of DOMGenerator we get this result. It would * be better if there was code that made this happen explicitly. */ public final class DOMGenerator extends ProcessorImpl { /** * Abstraction that lets use either DOM4J or W3C document as source. */ private static abstract class SourceFactory { private final String systemID; protected SourceFactory(final String sid) { systemID = sid; } abstract Source makeDOMSource(); final Source makeSource() { final Source ret = makeDOMSource(); ret.setSystemId(systemID); return ret; } } private static class DOM4JSourceFactory extends SourceFactory { private final org.orbeon.dom.Document doc; DOM4JSourceFactory(final org.orbeon.dom.Document d, final String sid, boolean clone) { super(sid); doc = clone ? (org.orbeon.dom.Document) d.clone() : d; } Source makeDOMSource() { return Dom4jUtils.getDocumentSource(doc); } } private static class W3CSourceFactory extends SourceFactory { private final org.w3c.dom.Document doc; W3CSourceFactory(final org.w3c.dom.Document d, final String sid) { super(sid); if (d == null) throw new OXFException("Document d == null"); doc = (org.w3c.dom.Document) d.cloneNode(true); } Source makeDOMSource() { return new DOMSource(doc); } } private static class TinyTreeSourceFactory extends SourceFactory { private final DocumentInfo documentInfo; TinyTreeSourceFactory(final DocumentInfo documentInfo, final String systemId) { super(systemId); if (documentInfo == null) throw new OXFException("Document documentInfo == null"); this.documentInfo = documentInfo; } Source makeDOMSource() { return documentInfo; } } private static class DocKey extends SimpleOutputCacheKey { public DocKey(final String id) { super(DOMGenerator.class, OUTPUT_DATA, id); } @Override public String toString() { return "DocKey [ " + super.toString() + " ]"; } @Override public boolean equals(final Object other) { // ??? return other == this; } } public final static Long ZeroValidity = (long) 0; public final static String DefaultContext = "oxf:/"; private static org.orbeon.dom.Document makeCopyDoc(final org.orbeon.dom.Element e) { final org.orbeon.dom.Element cpy = e.createCopy(); final Document ret = DocumentFactory.createDocument(); ret.setRootElement(cpy); return ret; } private static DocumentInfo makeCopyDoc(final NodeInfo nodeInfo) { if (nodeInfo instanceof DocumentInfo) return (DocumentInfo) nodeInfo; else return TransformerUtils.readTinyTree(XPath.GlobalConfiguration(), nodeInfo, false); } private final SourceFactory sourceFactory; private final DocKey key; private final Object validity; private DOMGenerator(final String id, final Object v, final SourceFactory srcFctry) { key = new DocKey(id); validity = v; sourceFactory = srcFctry; final ProcessorInputOutputInfo pInOutInf = new ProcessorInputOutputInfo(OUTPUT_DATA); addOutputInfo(pInOutInf); } /** * @param id Is really just for debugging purposes. Should give some clue as to who is * instantiating this DOMGenerator. * @param sid Base url used to resolve any relative URLs that may be contained within the * document. */ public DOMGenerator(final org.w3c.dom.Document d, final String id, Object v, final String sid) { this(id, v, new W3CSourceFactory(d, sid)); } /** * @param id Is really just for debugging purposes. Should give some clue as to who is * instantiating this DOMGenerator. * @param sid Base url used to resolve any relative URLs that may be contained within the * document. */ public DOMGenerator(final org.orbeon.dom.Element e, final String id, Object v, final String sid) { this(id, v, new DOM4JSourceFactory(makeCopyDoc(e), sid, false)); } /** * @param id Is really just for debugging purposes. Should give some clue as to who is * instantiating this DOMGenerator. * @param sid Base url used to resolve any relative URLs that may be contained within the * document. */ public DOMGenerator(final org.orbeon.dom.Document d, final String id, Object v, final String sid) { this(id, v, new DOM4JSourceFactory(d, sid, true)); } public DOMGenerator(final NodeInfo nodeInfo, final String id, Object v, final String sid) { this(id, v, new TinyTreeSourceFactory(makeCopyDoc(nodeInfo), sid)); } @Override public ProcessorOutput createOutput(final String nm) { final ProcessorOutput ret = new CacheableTransformerOutputImpl(DOMGenerator.this, nm) { public void readImpl(final PipelineContext pipelineContext, final XMLReceiver xmlReceiver) { // NOTE: source cannot be an instance var. Reason is that the XMLReader it // will create is stateful. ( Meaning that if it used by multiple threads // confusion will ensue. TransformerUtils.sourceToSAX(sourceFactory.makeSource(), xmlReceiver); } @Override public OutputCacheKey getKeyImpl(PipelineContext pipelineContext) { return key; } @Override public Object getValidityImpl(final PipelineContext pipelineContext) { return validity; } }; addOutput(nm, ret); return ret; } }