/*
* eXist Open Source Native XML Database
* Copyright (C) 2004-2009 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.
*
* $Id$
*/
package org.exist.xquery.functions;
import org.apache.log4j.Logger;
import org.exist.collections.Collection;
import org.exist.dom.DefaultDocumentSet;
import org.exist.dom.DocumentImpl;
import org.exist.dom.DocumentSet;
import org.exist.dom.MutableDocumentSet;
import org.exist.dom.NewArrayNodeSet;
import org.exist.dom.NodeProxy;
import org.exist.dom.NodeSet;
import org.exist.dom.QName;
import org.exist.dom.StoredNode;
import org.exist.numbering.NodeId;
import org.exist.storage.DBBroker;
import org.exist.storage.UpdateListener;
import org.exist.storage.lock.Lock;
import org.exist.util.LockException;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Cardinality;
import org.exist.xquery.Dependency;
import org.exist.xquery.Function;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.Profiler;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.xmldb.XMLDBModule;
import org.exist.xquery.value.AnyURIValue;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author wolf
*/
public class ExtCollection extends Function {
protected static final Logger logger = Logger.getLogger(ExtCollection.class);
public final static FunctionSignature signature =
new FunctionSignature(
new QName("collection", Function.BUILTIN_FUNCTION_NS),
"Returns the documents contained in the collections " +
"specified in the input sequence. " +
XMLDBModule.COLLECTION_URI +
" Documents contained in subcollections are also included.",
new SequenceType[] {
//Different from the offical specs
new FunctionParameterSequenceType("collection-uris", Type.STRING, Cardinality.ZERO_OR_MORE, "The collection-uris for which to include the documents")},
new FunctionReturnSequenceType(Type.NODE, Cardinality.ZERO_OR_MORE, "the document nodes contained in or under the given collections"),
true);
private boolean includeSubCollections = false;
private List cachedArgs = null;
private Sequence cached = null;
private UpdateListener listener = null;
/**
* @param context
*/
public ExtCollection(XQueryContext context) {
this(context, signature, true);
}
public ExtCollection(XQueryContext context, FunctionSignature signature, boolean inclusive) {
super(context, signature);
includeSubCollections = inclusive;
}
/* (non-Javadoc)
* @see org.exist.xquery.Expression#eval(org.exist.dom.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
*/
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
if (contextSequence != null)
context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence);
if (contextItem != null)
context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());
}
List args = getParameterValues(contextSequence, contextItem);
// TODO: disabled cache for now as it may cause concurrency issues
// better use compile-time inspection and maybe a pragma to mark those
// sections in the query that can be safely cached
// boolean cacheIsValid = false;
// if(cachedArgs != null)
// cacheIsValid = compareArguments(cachedArgs, args);
// if(cacheIsValid) {
// // if the expression occurs in a nested context, we might have cached the
// // document set
// if (context.getProfiler().isEnabled())
// context.getProfiler().end(this, "fn:collection: loading documents", cached);
// return cached;
// }
// build the document set
DocumentSet docs = null;
// DocumentSet docs = new DocumentSet(521);
try {
if (args.size() == 0) {
//TODO : add default collection to the context
//If the value of the default collection is undefined an error is raised [err:FODC0002].
//throw new XPathException("FODC0002: unknown collection '" + uri + "'");
docs = context.getStaticallyKnownDocuments();
} else {
MutableDocumentSet ndocs = new DefaultDocumentSet();
for (int i = 0; i < args.size(); i++) {
String next = (String)args.get(i);
XmldbURI uri = new AnyURIValue(next).toXmldbURI();
Collection coll = context.getBroker().getCollection(uri);
if(coll == null) {
if (context.isRaiseErrorOnFailedRetrieval()) {
logger.error("FODC0002: can not access collection '" + uri + "'");
throw new XPathException(this, "FODC0002: can not access collection '" + uri + "'");
}
} else {
if (context.inProtectedMode())
context.getProtectedDocs().getDocsByCollection(coll, includeSubCollections, ndocs);
else
coll.allDocs(context.getBroker(), ndocs, includeSubCollections, true, context.getProtectedDocs());
}
}
docs = ndocs;
}
} catch (XPathException e) { //From AnyURIValue constructor
e.setLocation(line, column);
logger.error("FODC0002: can not access collection '" + e.getMessage() + "'");
throw new XPathException(this, "FODC0002: " + e.getMessage(), e);
}
// iterate through all docs and create the node set
NodeSet result = new NewArrayNodeSet(docs.getDocumentCount(), 1);
Lock dlock;
DocumentImpl doc;
for (Iterator i = docs.getDocumentIterator(); i.hasNext();) {
doc = (DocumentImpl)i.next();
dlock = doc.getUpdateLock();
boolean lockAcquired = false;
try {
if (!context.inProtectedMode() && !dlock.hasLock()) {
dlock.acquire(Lock.READ_LOCK);
lockAcquired = true;
}
result.add(new NodeProxy(doc)); // , -1, Node.DOCUMENT_NODE));
} catch (LockException e) {
logger.error("Could not acquire lock on document " + doc.getURI());
} finally {
if (lockAcquired)
dlock.release(Lock.READ_LOCK);
}
}
cached = result;
cachedArgs = args;
registerUpdateListener();
if (context.getProfiler().isEnabled())
context.getProfiler().end(this, "", result);
return result;
}
/**
* @param contextSequence
* @param contextItem
* @throws XPathException
*/
private List getParameterValues(Sequence contextSequence, Item contextItem) throws XPathException {
List args = new ArrayList(getArgumentCount() + 10);
for(int i = 0; i < getArgumentCount(); i++) {
Sequence seq =
getArgument(i).eval(contextSequence, contextItem);
for (SequenceIterator j = seq.iterate(); j.hasNext();) {
Item next = j.nextItem();
args.add(next.getStringValue());
}
}
return args;
}
private boolean compareArguments(List args1, List args2) {
if(args1.size() != args2.size())
return false;
for(int i = 0; i < args1.size(); i++) {
String arg1 = (String)args1.get(i);
String arg2 = (String)args2.get(i);
if(!arg1.equals(arg2))
return false;
}
return true;
}
protected void registerUpdateListener() {
if (listener == null) {
listener = new UpdateListener() {
public void documentUpdated(DocumentImpl document, int event) {
// clear all
cached = null;
cachedArgs = null;
}
public void unsubscribe() {
ExtCollection.this.listener = null;
}
public void nodeMoved(NodeId oldNodeId, StoredNode newNode) {
// not relevant
}
public void debug() {
LOG.debug("UpdateListener: Line: " + getLine() + ": " + ExtCollection.this.toString());
}
};
context.registerUpdateListener(listener);
}
}
/* (non-Javadoc)
* @see org.exist.xquery.PathExpr#resetState()
*/
public void resetState(boolean postOptimization) {
super.resetState(postOptimization);
cached = null;
cachedArgs = null;
}
}