/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-07 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.indexing;
import org.exist.collections.Collection;
import org.exist.dom.AttrImpl;
import org.exist.dom.DocumentImpl;
import org.exist.dom.ElementImpl;
import org.exist.dom.NodeProxy;
import org.exist.dom.StoredNode;
import org.exist.dom.TextImpl;
import org.exist.storage.DBBroker;
import org.exist.storage.NodePath;
import org.exist.storage.txn.Txn;
import org.exist.util.DatabaseConfigurationException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Internally used to dispatch an operation to each of the
* registered indexes. An IndexController instance can be
* retrieved via {@link org.exist.storage.DBBroker#getIndexController()}.
*
*/
public class IndexController {
protected Map indexWorkers = new HashMap();
protected DBBroker broker;
protected StreamListener listener = null;
protected DocumentImpl currentDoc = null;
protected int currentMode = StreamListener.UNKNOWN;
public IndexController(DBBroker broker) {
this.broker = broker;
IndexWorker[] workers = broker.getBrokerPool().getIndexManager().getWorkers(broker);
for (int i = 0; i < workers.length; i++) {
indexWorkers.put(workers[i].getIndexId(), workers[i]);
}
}
/**
* TODO: temporary method to plug in fulltext index.
* Remove once new fulltext index module is ready.
*
* @param worker
*/
public void addIndexWorker(IndexWorker worker) {
indexWorkers.put(worker.getIndexId(), worker);
}
/**
* Configures all index workers registered with the db instance.
*
* @param configNodes lists the top-level child nodes below the <index> element in collection.xconf
* @param namespaces the active prefix/namespace map
* @return an arbitrary configuration object to be kept for this index in the collection configuration
* @throws DatabaseConfigurationException if a configuration error occurs
*/
public Map configure(NodeList configNodes, Map namespaces) throws DatabaseConfigurationException {
Map map = new HashMap();
IndexWorker indexWorker;
Object conf;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
indexWorker = (IndexWorker) i.next();
conf = indexWorker.configure(this, configNodes, namespaces);
if (conf != null)
map.put(indexWorker.getIndexId(), conf);
}
return map;
}
/**
* Returns an {@link org.exist.indexing.IndexWorker} instance corresponding
* to the specified type of index in indexId. The indexId should be the same one
* as returned by {@link org.exist.indexing.IndexWorker#getIndexId()}.
*
* @param indexId
* @return instance of index worker
*/
public IndexWorker getWorkerByIndexId(String indexId) {
return (IndexWorker) indexWorkers.get(indexId);
}
/**
* Returns an {@link org.exist.indexing.IndexWorker} instance corresponding
* to the specified index named by indexName. The indexName should be the same one
* as returned by {@link org.exist.indexing.IndexWorker#getIndexName()}.
*
* @param indexName
* @return instance of index worker
*/
public IndexWorker getWorkerByIndexName(String indexName) {
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
IndexWorker worker = (IndexWorker) i.next();
if (indexName.equals(worker.getIndexName()))
return worker;
}
return null;
}
/**
* Sets the document for the next operation.
*
* @param doc the document
*/
public void setDocument(DocumentImpl doc) {
if (currentDoc != doc)
//Reset listener
listener = null;
currentDoc = doc;
IndexWorker indexWorker;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
indexWorker = (IndexWorker) i.next();
indexWorker.setDocument(currentDoc);
}
}
/**
* Sets the the mode for the next operation.
*
* @param mode the mode, one of {@link StreamListener#UNKNOWN}, {@link StreamListener#STORE},
* {@link StreamListener#REMOVE_SOME_NODES} or {@link StreamListener#REMOVE_ALL_NODES}.
*/
public void setMode(int mode) {
if (currentMode != mode)
//Reset listener
listener = null;
currentMode = mode;
IndexWorker indexWorker;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
indexWorker = (IndexWorker) i.next();
indexWorker.setMode(currentMode);
}
}
/**
* Returns the document for the next operation.
*
* @return the document
*/
public DocumentImpl getDocument() {
return currentDoc;
}
/**
* Returns the mode for the next operation.
*
* @return the document
*/
public int getMode() {
return currentMode;
}
/**
* Sets the document and the mode for the next operation.
*
* @param doc the document
* @param mode the mode, one of {@link StreamListener#UNKNOWN}, {@link StreamListener#STORE},
* {@link StreamListener#REMOVE_SOME_NODES} or {@link StreamListener#REMOVE_ALL_NODES}.
*/
public void setDocument(DocumentImpl doc, int mode) {
setDocument(doc);
setMode(mode);
}
/**
* Flushes all index workers.
*/
public void flush() {
IndexWorker indexWorker;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
indexWorker = (IndexWorker) i.next();
indexWorker.flush();
}
}
/**
* Remove all indexes defined on the specified collection.
*
* @param collection the collection to remove
* @param broker the broker that will perform the operation
*/
public void removeCollection(Collection collection, DBBroker broker) {
IndexWorker indexWorker;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
indexWorker = (IndexWorker) i.next();
indexWorker.removeCollection(collection, broker);
}
}
/**
* Reindex all nodes below the specified root node, using the given mode.
*
* @param transaction the current transaction
* @param reindexRoot the node from which reindexing should occur
* @param mode the mode, one of {@link StreamListener#UNKNOWN}, {@link StreamListener#STORE},
* {@link StreamListener#REMOVE_SOME_NODES} or {@link StreamListener#REMOVE_ALL_NODES}.
*/
public void reindex(Txn transaction, StoredNode reindexRoot, int mode) {
if (reindexRoot == null)
return;
reindexRoot = broker.objectWith(new NodeProxy(reindexRoot.getDocument(), reindexRoot.getNodeId()));
setDocument(reindexRoot.getDocument(), mode);
getStreamListener();
IndexUtils.scanNode(broker, transaction, reindexRoot, listener);
flush();
}
/**
* When adding or removing nodes to or from the document tree, it might become
* necessary to reindex some parts of the tree, in particular if indexes are defined
* on mixed content nodes. This method will call
* {@link IndexWorker#getReindexRoot(org.exist.dom.StoredNode, org.exist.storage.NodePath, boolean)}
* on each configured index. It will then return the top-most root.
*
* @param node the node to be modified.
* @param path the NodePath of the node
* @return the top-most root node to be reindexed
*/
public StoredNode getReindexRoot(StoredNode node, NodePath path) {
return getReindexRoot(node, path, false);
}
/**
* When adding or removing nodes to or from the document tree, it might become
* necessary to reindex some parts of the tree, in particular if indexes are defined
* on mixed content nodes. This method will call
* {@link IndexWorker#getReindexRoot(org.exist.dom.StoredNode, org.exist.storage.NodePath, boolean)}
* on each configured index. It will then return the top-most root.
*
* @param node the node to be modified.
* @param path path the NodePath of the node
* @param includeSelf if set to true, the current node itself will be included in the check
* @return the top-most root node to be reindexed
*/
public StoredNode getReindexRoot(StoredNode node, NodePath path, boolean includeSelf) {
IndexWorker indexWorker;
StoredNode next, top = null;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
indexWorker = (IndexWorker) i.next();
next = indexWorker.getReindexRoot(node, path, includeSelf);
if (next != null && (top == null || top.getNodeId().isDescendantOf(next.getNodeId())))
top = next;
}
if (top != null && top.getNodeId().equals(node.getNodeId()))
top = node;
return top;
}
/**
* Returns a chain of {@link org.exist.indexing.StreamListener}, one
* for each index configured on the current document for the current mode.
* Note that the chain is reinitialized when the operating mode changes.
* That allows workers to return different {@link org.exist.indexing.StreamListener}
* for each mode.
*
* @return the first listener in the chain of StreamListeners
*/
public StreamListener getStreamListener() {
if (listener != null) {
StreamListener next = listener;
while (next != null) {
// wolf: setDocument() should have been called before
// next.getWorker().setDocument(currentDoc, currentMode);
next = next.getNextInChain();
}
return listener;
}
StreamListener first = null;
StreamListener current, previous = null;
IndexWorker worker;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext();) {
worker = (IndexWorker) i.next();
// wolf: setDocument() should have been called before
// worker.setDocument(currentDoc, currentMode);
current = worker.getListener();
if (first == null) {
first = current;
} else {
if (current != null)
previous.setNextInChain(current);
}
if (current != null)
previous = current;
}
listener = first;
return listener;
}
/**
* Helper method: index a single node which has been added during an XUpdate or XQuery update expression.
*
* @param transaction the current transaction
* @param node the node to index
* @param path the node's NodePath
* @param listener the StreamListener which receives the index events
*/
public void indexNode(Txn transaction, StoredNode node, NodePath path, StreamListener listener) {
if (listener != null) {
switch (node.getNodeType()) {
case Node.ELEMENT_NODE:
listener.startElement(transaction, (ElementImpl) node, path);
break;
case Node.TEXT_NODE :
listener.characters(transaction, (TextImpl) node, path);
break;
case Node.ATTRIBUTE_NODE :
listener.attribute(transaction, (AttrImpl) node, path);
break;
}
}
}
/**
* Helper method: index a single element node which has been added during an XUpdate or XQuery update expression.
*
* @param transaction the current transaction
* @param node the node to index
* @param path the node's NodePath
* @param listener the StreamListener which receives the index events
*/
public void startElement(Txn transaction, ElementImpl node, NodePath path, StreamListener listener) {
if (listener != null)
listener.startElement(transaction, node, path);
}
/**
* Helper method: dispatch a single endElement event to the specified listener.
*
* @param transaction the current transaction
* @param node the node to index
* @param path the node's NodePath
* @param listener the StreamListener which receives index events
*/
public void endElement(Txn transaction, ElementImpl node, NodePath path, StreamListener listener) {
if (listener != null)
listener.endElement(transaction, node, path);
}
/**
* Helper method: index a single attribute node which has been added during an XUpdate or XQuery update expression.
*
* @param transaction the current transaction
* @param node the node to index
* @param path the node's NodePath
* @param listener the StreamListener which receives the index events
*/
public void attribute(Txn transaction, AttrImpl node, NodePath path, StreamListener listener) {
if (listener != null)
listener.attribute(transaction, node, path);
}
/**
* Helper method: index a single text node which has been added during an XUpdate or XQuery update expression.
*
* @param transaction the current transaction
* @param node the node to index
* @param path the node's NodePath
* @param listener the StreamListener which receives the index events
*/
public void characters(Txn transaction, TextImpl node, NodePath path, StreamListener listener) {
if (listener != null)
listener.characters(transaction, node, path);
}
/**
* Returns the match listener for this node.
*
* @param proxy a proxy to the node.
* @return the MatchListener
*/
public MatchListener getMatchListener(NodeProxy proxy) {
MatchListener first = null;
MatchListener current, previous = null;
IndexWorker worker;
for (Iterator i = indexWorkers.values().iterator(); i.hasNext(); ) {
worker = (IndexWorker) i.next();
current = worker.getMatchListener(broker, proxy);
if (current != null) {
if (first == null) {
first = current;
} else {
previous.setNextInChain(current);
}
previous = current;
}
}
return first;
}
}