/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: CollectionImpl.java 564833 2007-08-11 04:25:23Z vgritsenko $
*/
package org.apache.xindice.client.xmldb.embed;
import org.apache.xindice.client.xmldb.ResourceSetImpl;
import org.apache.xindice.client.xmldb.XindiceCollection;
import org.apache.xindice.client.xmldb.resources.BinaryResourceImpl;
import org.apache.xindice.client.xmldb.resources.XMLResourceImpl;
import org.apache.xindice.core.Collection;
import org.apache.xindice.core.Database;
import org.apache.xindice.core.FaultCodes;
import org.apache.xindice.core.data.Entry;
import org.apache.xindice.core.data.NodeSet;
import org.apache.xindice.core.meta.MetaData;
import org.apache.xindice.core.query.QueryUtil;
import org.apache.xindice.util.Configuration;
import org.apache.xindice.xml.TextWriter;
import org.apache.xindice.xml.dom.DocumentImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xmldb.api.base.ErrorCodes;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.BinaryResource;
import org.xmldb.api.modules.XMLResource;
import java.util.Hashtable;
import java.util.StringTokenizer;
/**
* Implementation of XML:DB's <code>Collection</code> interface using
* direct access to interact with database server
*
* @author <a href="mailto:james.bates@amplexor.com">James Bates</a>
* @author <a href="mailto:kstaken@xmldatabases.org">Kimbro Staken</a>
* @version $Revision: 564833 $, $Date: 2007-08-11 00:25:23 -0400 (Sat, 11 Aug 2007) $
*/
public class CollectionImpl extends XindiceCollection {
private Database db;
private Collection col;
/**
* Creates new <code>CollectionImpl</code> instance representing connection
* to server collection.
*
* @param db Database this collection resides in
* @param collPath Canonical path of this collection (including database name)
* @exception XMLDBException thrown if a connection could not be established,
* because of URL syntax errors, or connection failure, or if no
* collection with path <code>collPath</code> could be located.
*/
public CollectionImpl(Database db, String collPath) throws XMLDBException {
super(collPath);
this.db = db;
// Extract path of the collection within database
String collName = "/";
int colIndex = collPath.indexOf('/', 1);
if (colIndex != -1) {
collName = collPath.substring(colIndex);
if (collName.equals("")) {
collName = "/";
}
}
try {
this.col = db.getCollection(collName);
} catch (Exception e) {
throw FaultCodes.createXMLDBException(ErrorCodes.INVALID_COLLECTION,
"Collection not available: " + collPath, e);
}
if (this.col == null) {
throw new XMLDBException(ErrorCodes.NO_SUCH_COLLECTION,
"Collection not found: " + collPath);
}
}
/**
* Retrieves a <code>Resource</code> from the database. If the
* <code>Resource</code> could not be
* located a null value will be returned.
*
* @param id the unique id for the requested resource.
* @return The retrieved <code>Resource</code> instance.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public Resource getResource(String id) throws XMLDBException {
checkOpen();
try {
Entry entry = col.getEntry(id);
if (entry == null) {
return null;
}
switch (entry.getEntryType()) {
case Entry.DOCUMENT:
DocumentImpl doc = (DocumentImpl) entry.getValue();
// This should probably just pass the document.
if (doc.getDataBytes() == null) {
return new XMLResourceImpl(id, id, this, TextWriter.toString(doc));
} else {
return new XMLResourceImpl(id, id, this, doc.getSymbols(), doc.getDataBytes());
}
case Entry.BINARY:
return new BinaryResourceImpl(id, this, (byte[]) entry.getValue());
default:
throw new XMLDBException(ErrorCodes.UNKNOWN_RESOURCE_TYPE,
"Internal error: Unexpected resource type " + entry.getEntryType());
}
} catch (Exception e) {
throw FaultCodes.createXMLDBException("Resource not available: " + id, e);
}
}
/**
* Returns the number of resources currently stored in this collection or 0
* if the collection is empty.
*
* @return the number of resource in the collection.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public int getResourceCount() throws XMLDBException {
checkOpen();
try {
return (int) col.getDocumentCount();
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/**
* Stores the provided resource into the database. If the resource does not
* already exist it will be created. If it does already exist it will be
* updated.
*
* @param res the resource to store in the database.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.INVALID_RESOURCE</code> if the <code>Resource</code> is
* not valid.
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public void storeResource(Resource res) throws XMLDBException {
if (res.getContent() == null) {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE,
"No resource data");
}
checkOpen();
if (res instanceof BinaryResource) {
Object content = res.getContent();
byte[] bytes;
if (content instanceof byte[]) {
bytes = (byte[]) content;
} else {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE,
"The contents of a binary resource must have type byte[].");
}
try {
if (res.getId() != null) {
col.setBinary(res.getId(), bytes);
} else {
String name = col.insertBinary(bytes).toString();
((BinaryResourceImpl) res).setId(name);
}
} catch (Exception e) {
throw FaultCodes.createXMLDBException(ErrorCodes.INVALID_RESOURCE,
"Invalid resource:" + res.getId(), e);
}
} else if (res instanceof XMLResource) {
try {
Node content = ((XMLResourceImpl) res).getContentAsDOM();
if (content != null && content instanceof Document) {
if (res.getId() != null) {
col.setDocument(res.getId(), (Document) content);
} else {
String name = col.insertDocument((Document) content).toString();
((XMLResourceImpl) res).setId(name);
}
} else {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE,
"A resource must be a document in order to be stored.");
}
} catch (Exception e) {
throw FaultCodes.createXMLDBException(ErrorCodes.INVALID_RESOURCE,
"Invalid resource: " + res.getId(), e);
}
} else {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE,
"Only XMLResource and BinaryResource supported");
}
}
/* see superclass for documentation */
public boolean isOpen() {
return (col != null);
}
/* see superclass for documentation */
public String getURI() {
return "xmldb:" + DatabaseImpl.DRIVER_NAME + "://" + getCanonicalName();
}
/**
* Returns a <code>Collection</code> instance for the requested child collection
* if it exists.
*
* @param name the name of the child collection to retrieve.
* @return the requested child collection or null if it couldn't be found.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public org.xmldb.api.base.Collection getChildCollection(String name) throws XMLDBException {
if (name.indexOf('/') != -1) {
throw new XMLDBException(ErrorCodes.INVALID_COLLECTION,
"Invalid collection: " + name);
}
try {
return new CollectionImpl(db, getCanonicalName() + "/" + name);
} catch (XMLDBException e) {
if (e.errorCode == ErrorCodes.NO_SUCH_COLLECTION) {
// per getChildCollection contract, return null if not found
return null;
}
throw e;
} catch (Exception e) {
throw FaultCodes.createXMLDBException(ErrorCodes.INVALID_COLLECTION,
"Invalid collection: " + name, e);
}
}
/**
* Creates a new unique ID within the context of the <code>Collection</code>
*
* @return the created id as a string.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public String createId() throws XMLDBException {
checkOpen();
return col.createNewOID().toString();
}
/**
* Releases all resources consumed by the <code>Collection</code>.
* The <code>close</code> method must
* always be called when use of a <code>Collection</code> is complete. It is
* not safe to use a <code>Collection</code> after the <code>close</code>
* method has been called.
*
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
*/
public void close() throws XMLDBException {
col = null;
// FIXME Should not be necessary here:
db.flushConfig();
}
/**
* Returns the parent collection for this collection or null if no parent
* collection exists.
*
* @return the parent <code>Collection</code> instance.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public org.xmldb.api.base.Collection getParentCollection() throws XMLDBException {
// If there's only one slash then it's the root.
if (collPath.lastIndexOf("/") == 0) {
return null;
}
try {
return new CollectionImpl(db, collPath.substring(0, collPath.lastIndexOf('/')));
} catch (XMLDBException e) {
if (e.errorCode == ErrorCodes.NO_SUCH_COLLECTION) {
// per getParentCollection contract, return null if no parent
return null;
}
throw e;
}
}
/**
* Removes the <code>Resource</code> from the database.
*
* @param res the resource to remove.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.INVALID_RESOURCE</code> if the <code>Resource</code> is
* not valid.<br />
* <code>ErrorCodes.NO_SUCH_RESOURCE</code> if the <code>Resource</code> is
* not known to this <code>Collection</code>.
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public void removeResource(Resource res) throws XMLDBException {
if (res == null || res.getId() == null || res.getId().length() == 0) {
// Query result resource will have null ID
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE,
"Resource passed is null or its ID is empty.");
}
checkOpen();
try {
col.remove(res.getId());
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/**
* Returns a list of collection names naming all child collections
* of the current collection. If no child collections exist an empty list is
* returned.
*
* @return an array containing collection names for all child
* collections.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public String[] listChildCollections() throws XMLDBException {
checkOpen();
try {
return col.listCollections();
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/**
* Returns the number of child collections under this
* <code>Collection</code> or 0 if no child collections exist.
*
* @return the number of child collections.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public int getChildCollectionCount() throws XMLDBException {
checkOpen();
try {
return (int) col.countCollections();
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/**
* Returns a list of the ids for all resources stored in the collection.
*
* @return a string array containing the names for all
* <code>Resource</code>s in the collection.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrorCodes.COLLECTION_CLOSED</code> if the <code>close</code>
* method has been called on the <code>Collection</code><br />
*/
public String[] listResources() throws XMLDBException {
checkOpen();
try {
return col.listDocuments();
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/* see superclass for documentation */
public ResourceSet query(String name, String queryLang, String query, Hashtable nsMap) throws XMLDBException {
checkOpen();
try {
NodeSet nodeSet;
if (name != null) {
nodeSet = col.queryDocument(queryLang, query, QueryUtil.mapNamespaces(nsMap), name);
} else {
nodeSet = col.queryCollection(queryLang, query, QueryUtil.mapNamespaces(nsMap));
}
return new ResourceSetImpl(this, QueryUtil.queryResultsToDOM(nodeSet));
} catch (Exception e) {
throw FaultCodes.createXMLDBException(FaultCodes.QRY_PROCESSING_ERROR,
"Query error: " + e.getMessage(), e);
}
}
/* see superclass for documentation */
public org.xmldb.api.base.Collection createCollection(String name) throws XMLDBException {
checkOpen();
try {
Document doc = new DocumentImpl();
Element colEle = doc.createElement("collection");
colEle.setAttribute("compressed", "true");
colEle.setAttribute("name", name);
doc.appendChild(colEle);
Element filEle = doc.createElement("filer");
filEle.setAttribute("class", "org.apache.xindice.core.filer.BTreeFiler");
colEle.appendChild(filEle);
return createCollection(name, doc);
} catch (Exception e) {
throw FaultCodes.createXMLDBException(ErrorCodes.INVALID_COLLECTION,
FaultCodes.GEN_UNKNOWN,
"Cannot create child collection", e);
}
}
// See superclass for documentation. Note that first argument is the path, not the name.
public org.xmldb.api.base.Collection createCollection(String path, Document configuration) throws XMLDBException {
checkOpen();
try {
Configuration config = new Configuration(configuration.getDocumentElement(), false);
col.createCollection(path, config);
// Traverse path to get newly created collection
org.xmldb.api.base.Collection col = this;
if (path.indexOf("/") != -1) {
StringTokenizer st = new StringTokenizer(path, "/");
while (col != null && st.hasMoreTokens()) {
path = st.nextToken().trim();
if (path.length() == 0) {
continue;
}
if (st.hasMoreTokens()) {
col = col.getChildCollection(path);
} else {
break;
}
}
}
return col.getChildCollection(path);
} catch (Exception e) {
throw FaultCodes.createXMLDBException(ErrorCodes.INVALID_COLLECTION,
FaultCodes.GEN_UNKNOWN,
"Cannot create child collection", e);
}
}
/* see superclass for documentation */
public void removeCollection(String childName) throws XMLDBException {
if (null == childName || childName.length() == 0) {
throw new XMLDBException(ErrorCodes.NO_SUCH_COLLECTION,
FaultCodes.COL_COLLECTION_NOT_FOUND,
"Cannot remove child collection '" + childName + "': Name is empty");
}
checkOpen();
try {
col.dropCollection(col.getCollection(childName));
} catch (Exception e) {
throw FaultCodes.createXMLDBException(ErrorCodes.INVALID_COLLECTION,
FaultCodes.GEN_UNKNOWN,
"Cannot remove child collection '" + childName + "'", e);
}
}
/* see superclass for documentation */
public String[] listIndexers() throws XMLDBException {
checkOpen();
try {
return col.listIndexers();
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/* see superclass for documentation */
public void createIndexer(Document configuration) throws XMLDBException {
checkOpen();
try {
col.createIndexer(new Configuration(configuration, false));
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/* see superclass for documentation */
public void dropIndexer(String name) throws XMLDBException {
checkOpen();
try {
col.dropIndexer(col.getIndexer(name));
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/* see superclass for documentation */
public void shutdown() throws XMLDBException {
try {
db.close();
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
public MetaData getMetaData(String id) throws XMLDBException {
try {
if (id == null) {
return col.getCollectionMeta();
} else {
return col.getDocumentMeta(id);
}
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
public void setMetaData(String id, MetaData meta) throws XMLDBException {
try {
if (id == null) {
col.setCollectionMeta(meta);
} else {
col.setDocumentMeta(id, meta);
}
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
}