/* * eXist Open Source Native XML Database * Copyright (C) 2001-2015 The eXist Project * http://exist-db.org * * 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 * 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. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.exist.xmldb; import java.io.IOException; import java.io.StringWriter; import java.util.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.Namespaces; import org.exist.dom.persistent.NodeProxy; import org.exist.dom.persistent.SortedNodeSet; import org.exist.security.Subject; import org.exist.storage.BrokerPool; import org.exist.storage.serializers.Serializer; import org.exist.util.serializer.SAXSerializer; import org.exist.util.serializer.SerializerPool; import org.exist.xquery.XPathException; import org.exist.xquery.value.AtomicValue; import org.exist.xquery.value.BinaryValue; import org.exist.xquery.value.Item; import org.exist.xquery.value.NodeValue; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.SequenceIterator; import org.exist.xquery.value.Type; import org.exist.xquery.value.ValueSequence; import org.w3c.dom.Node; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.xmldb.api.base.ErrorCodes; import org.xmldb.api.base.Resource; import org.xmldb.api.base.ResourceIterator; import org.xmldb.api.base.ResourceSet; import org.xmldb.api.base.XMLDBException; public class LocalResourceSet extends AbstractLocal implements ResourceSet { private final static Logger LOG = LogManager.getLogger(LocalResourceSet.class); private final List<Object> resources = new ArrayList<>(); private final Properties outputProperties; public LocalResourceSet(final Subject user, final BrokerPool pool, final LocalCollection col, final Properties properties, final Sequence val, final String sortExpr) throws XMLDBException { super(user, pool, col); this.outputProperties = properties; if(val.isEmpty()) { return; } final Sequence seq; if(Type.subTypeOf(val.getItemType(), Type.NODE) && sortExpr != null) { final SortedNodeSet sorted = new SortedNodeSet(brokerPool, user, sortExpr); try { sorted.addAll(val); } catch (final XPathException e) { throw new XMLDBException(ErrorCodes.INVALID_RESOURCE, e.getMessage(), e); } seq = sorted; } else { seq = val; } try { for(final SequenceIterator i = seq.iterate(); i.hasNext(); ) { final Item item = i.nextItem(); resources.add(item); } } catch (final XPathException e) { throw new XMLDBException(ErrorCodes.INVALID_RESOURCE, e.getMessage(), e); } } @Override public void addResource(final Resource resource) throws XMLDBException { resources.add(resource); } @Override public void clear() throws XMLDBException { //cleanup any binary values resources.stream().filter((resource) -> (resource instanceof BinaryValue)).forEach((resource) -> { try { ((BinaryValue) resource).close(); } catch(final IOException ioe) { LOG.warn("Unable to cleanup BinaryValue: " + resource.hashCode(), ioe); } }); resources.clear(); } @Override public ResourceIterator getIterator() throws XMLDBException { return new NewResourceIterator(); } public ResourceIterator getIterator(final long start) throws XMLDBException { return new NewResourceIterator(start); } @Override public Resource getMembersAsResource() throws XMLDBException { final SAXSerializer handler = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class); final StringWriter writer = new StringWriter(); handler.setOutput(writer, outputProperties); return this.<Resource>withDb((broker, transaction) -> { try { // configure the serializer final Serializer serializer = broker.getSerializer(); serializer.reset(); collection.setProperty(Serializer.GENERATE_DOC_EVENTS, "false"); serializer.setProperties(outputProperties); serializer.setUser(user); serializer.setSAXHandlers(handler, handler); // serialize results handler.startDocument(); handler.startPrefixMapping("exist", Namespaces.EXIST_NS); final AttributesImpl attribs = new AttributesImpl(); attribs.addAttribute("", "hitCount", "hitCount", "CDATA", Integer.toString(resources.size())); handler.startElement(Namespaces.EXIST_NS, "result", "exist:result", attribs); Item current; char[] value; for (final Iterator<Object> i = resources.iterator(); i.hasNext(); ) { current = (Item) i.next(); if (Type.subTypeOf(current.getType(), Type.NODE)) { ((NodeValue) current).toSAX(broker, handler, outputProperties); } else { value = current.toString().toCharArray(); handler.characters(value, 0, value.length); } } handler.endElement(Namespaces.EXIST_NS, "result", "exist:result"); handler.endPrefixMapping("exist"); handler.endDocument(); final Resource res = new LocalXMLResource(user, brokerPool, collection, XmldbURI.EMPTY_URI); res.setContent(writer.toString()); SerializerPool.getInstance().returnObject(handler); return res; } catch (final SAXException e) { throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, "serialization error", e); } }); } @Override public Resource getResource(final long pos) throws XMLDBException { if (pos < 0 || pos >= resources.size()) { return null; } final Object r = resources.get((int) pos); LocalXMLResource res = null; if (r instanceof NodeProxy) { final NodeProxy p = (NodeProxy) r; // the resource might belong to a different collection // than the one by which this resource set has been // generated: adjust if necessary. LocalCollection coll = collection; if (p.getOwnerDocument().getCollection() == null || !coll.getPathURI().toCollectionPathURI().equals(p.getOwnerDocument().getCollection().getURI())) { coll = new LocalCollection(user, brokerPool, null, p.getOwnerDocument().getCollection().getURI()); coll.setProperties(outputProperties); } res = new LocalXMLResource(user, brokerPool, coll, p); } else if (r instanceof Node) { res = new LocalXMLResource(user, brokerPool, collection, XmldbURI.EMPTY_URI); res.setContentAsDOM((Node) r); } else if (r instanceof AtomicValue) { res = new LocalXMLResource(user, brokerPool, collection, XmldbURI.EMPTY_URI); res.setContent(r); } else if (r instanceof Resource) { return (Resource)r; } res.setProperties(outputProperties); return res; } public Sequence toSequence() { if (resources.isEmpty()) { return Sequence.EMPTY_SEQUENCE; } else if (resources.size() == 1) { return ((Item) resources.get(0)).toSequence(); } else { final ValueSequence s = new ValueSequence(); for (Object resource : resources) { final Item item = (Item) resource; s.add(item); } return s; } } @Override public long getSize() throws XMLDBException { return resources.size(); } @Override public void removeResource(final long pos) throws XMLDBException { resources.remove(pos); } class NewResourceIterator implements ResourceIterator { long pos = 0; public NewResourceIterator() { } public NewResourceIterator(final long start) { pos = start; } @Override public boolean hasMoreResources() throws XMLDBException { return pos < getSize(); } @Override public Resource nextResource() throws XMLDBException { return getResource(pos++); } } }