/*
* 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 511426 2007-02-25 03:25:02Z vgritsenko $
*/
package org.apache.xindice.client.xmldb.xmlrpc;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.client.xmldb.ResourceSetImpl;
import org.apache.xindice.client.xmldb.XindiceCollection;
import org.apache.xindice.client.xmldb.resources.XMLResourceImpl;
import org.apache.xindice.client.xmldb.resources.BinaryResourceImpl;
import org.apache.xindice.core.FaultCodes;
import org.apache.xindice.core.meta.MetaData;
import org.apache.xindice.server.rpc.RPCDefaultMessage;
import org.apache.xindice.server.rpc.RPCMessageInterface;
import org.apache.xindice.util.SymbolDeserializer;
import org.apache.xindice.xml.TextWriter;
import org.apache.xindice.xml.dom.DOMParser;
import org.apache.xindice.xml.dom.DocumentImpl;
import org.apache.xmlrpc.XmlRpcClient;
import org.apache.xmlrpc.XmlRpcException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xmldb.api.base.Collection;
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.XMLResource;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.StringReader;
import java.util.Hashtable;
import java.util.Vector;
import java.util.StringTokenizer;
/**
* Implementation of XML:DB's <code>Collection</code> interface using
* XML-RPC 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: 511426 $, $Date: 2007-02-24 22:25:02 -0500 (Sat, 24 Feb 2007) $
*/
public class CollectionImpl extends XindiceCollection {
private static final Log log = LogFactory.getLog(CollectionImpl.class);
/**
* The XML-RPC client stub, connected to the server
*/
private XmlRpcClient client = null;
/**
* Creates new <code>CollectionImpl</code> instance representing connection
* to server collection.
*
* @param client XML-RPC client connected to the Xindice XML-RPC server.
* @param collPath is the name of the collection to open.
* @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(XmlRpcClient client, String collPath) throws XMLDBException {
super(collPath);
this.client = client;
/* Just check the collection does actually exist */
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
String exists = (String) runRemoteCommand("GetCollectionConfiguration", params);
if (!"yes".equals(exists)) {
throw new XMLDBException(ErrorCodes.NO_SUCH_COLLECTION,
FaultCodes.COL_COLLECTION_NOT_FOUND,
"Collection not found: " + collPath);
}
}
/**
* Submits a command for RPC to database server
*
* @param cmdName command name
* @param params hashtable containing named parameters to send to server
* @return the return value from the server. Type of return value depends on
* command.
*
* @exception XMLDBException thrown if XML-RPC reports an exception.
*/
private Object runRemoteCommand(String cmdName, Hashtable params) throws XMLDBException {
try {
params.put(RPCMessageInterface.MESSAGE_PARAM, cmdName);
Vector v = new Vector();
v.add(params);
return ((Hashtable) client.execute("run", v)).get(RPCDefaultMessage.RESULT);
} catch (XmlRpcException e) {
if (log.isDebugEnabled()) {
log.debug("Got XmlRpc exception running command " + cmdName + ", code: " + e.code + ", msg: " + e.getMessage());
}
if (e.code != 0) {
throw new XMLDBException(e.code / FaultCodes.MAX_CODE,
e.code % FaultCodes.MAX_CODE,
e.getMessage());
}
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, FaultCodes.GEN_GENERAL_ERROR,
"Failed to execute command '" + cmdName + "' on server: " + client.getURL() + ", message: " + e.getMessage(), e);
} catch (IOException e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, FaultCodes.GEN_GENERAL_ERROR,
"Cannot communicate with the server: " + client.getURL(), e);
}
}
/**
* 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 {
if (id == null) {
return null;
}
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.NAME, id);
params.put(RPCDefaultMessage.COMPRESSED, "true");
Object result = runRemoteCommand("GetResource", params);
if (result == null) {
// No resource found
return null;
} else if (result instanceof Hashtable) {
// Result is compressed XML.
Hashtable compressed = (Hashtable) result;
SymbolDeserializer symbolDeserial = new SymbolDeserializer();
return new XMLResourceImpl(id, id, this, symbolDeserial.getSymbols(compressed), (byte[]) compressed.get("document"));
} else if (result instanceof byte[]) {
// Result is binary.
return new BinaryResourceImpl(id, this, (byte[]) result);
} else {
// Result is XML.
return new XMLResourceImpl(id, this, (String) result);
}
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, 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 {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
return ((Integer) runRemoteCommand("GetDocumentCount", params)).intValue();
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, 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();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.NAME, res.getId());
params.put(RPCDefaultMessage.DOCUMENT, res.getContent());
String name = (String) runRemoteCommand("InsertResource", params);
if (res instanceof XMLResource) {
((XMLResourceImpl) res).setId(name);
} else {
((BinaryResourceImpl) res).setId(name);
}
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
}
}
/* see superclass for documentation */
public boolean isOpen() {
return (client != null);
}
/* see superclass for documentation */
public String getURI() {
return "xmldb:" + DatabaseImpl.DRIVER_NAME + "://" +
client.getURL().getHost() + ':' + client.getURL().getPort() +
collPath;
}
/**
* 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 Collection getChildCollection(String name) throws XMLDBException {
if (name.indexOf('/') != -1) {
throw new XMLDBException(ErrorCodes.INVALID_COLLECTION);
}
try {
return new CollectionImpl(client, collPath + "/" + name);
} catch (XMLDBException e) {
if (e.errorCode == ErrorCodes.NO_SUCH_COLLECTION) {
// per getChildCollection contract, return null if not found
return null;
}
throw 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();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
return (String) runRemoteCommand("CreateNewOID", params);
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
}
}
/**
* 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 {
client = null;
}
/**
* 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 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(client, 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 {
// TODO: Test BinaryResource workings
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.NAME, res.getId());
runRemoteCommand("RemoveDocument", params);
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.NO_SUCH_RESOURCE, 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 {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
Vector list = (Vector) runRemoteCommand("ListCollections", params);
return (String[]) list.toArray(new String[list.size()]);
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, 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 {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
Integer result = (Integer) runRemoteCommand("GetCollectionCount", params);
return result.intValue();
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, 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 {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
Vector list = (Vector) runRemoteCommand("ListDocuments", params);
return (String[]) list.toArray(new String[list.size()]);
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e);
}
}
/* see superclass for documentation */
public ResourceSet query(String name, String queryLang, String query, Hashtable nsMap) throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.TYPE, queryLang);
params.put(RPCDefaultMessage.NAMESPACES, nsMap);
params.put(RPCDefaultMessage.QUERY, query);
if (name != null) {
params.put(RPCDefaultMessage.NAME, name);
}
String result = (String) runRemoteCommand("Query", params);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document resultDoc = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(result)));
ResourceSetImpl rs = new ResourceSetImpl(this, resultDoc);
return rs;
} catch (Exception e) {
e.printStackTrace();
throw FaultCodes.createXMLDBException(FaultCodes.QRY_PROCESSING_ERROR, "Query error", e);
}
}
/* see superclass for documentation */
public Collection createCollection(String name) throws XMLDBException {
return createCollection(name, null);
}
// See superclass for documentation. Note that first argument is the path, not the name.
public Collection createCollection(String path, Document configuration) throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.NAME, path);
if (configuration != null) {
params.put(RPCDefaultMessage.CONFIGURATION, TextWriter.toString(configuration));
}
runRemoteCommand("CreateCollection", params);
// 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 (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "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 {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.NAME, childName);
String result = (String) runRemoteCommand("RemoveCollection", params);
if (!result.equals("yes")) {
throw new XMLDBException(ErrorCodes.INVALID_COLLECTION,
"Cannot remove child collection '" + childName + "'");
}
} catch (XMLDBException x) {
throw x; // propagate any xmldb exception.
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.INVALID_COLLECTION,
"Cannot remove child collection '" + childName + "'", e);
}
}
/* see superclass for documentation */
public String[] listIndexers() throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
Vector list = (Vector) runRemoteCommand("ListIndexers", params);
return (String[]) list.toArray(new String[list.size()]);
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/* see superclass for documentation */
public void createIndexer(Document configuration) throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.CONFIGURATION, TextWriter.toString(configuration));
runRemoteCommand("CreateIndexer", params);
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/* see superclass for documentation */
public void dropIndexer(String name) throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
params.put(RPCDefaultMessage.NAME, name);
runRemoteCommand("RemoveIndexer", params);
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
/* see superclass for documentation */
public void shutdown() throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
runRemoteCommand("Shutdown", params);
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
public MetaData getMetaData(String id) throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
if (id != null) {
params.put(RPCDefaultMessage.NAME, id);
}
params.put(RPCDefaultMessage.COMPRESSED, "true");
Object result = runRemoteCommand(id == null ? "GetCollectionMeta" : "GetDocumentMeta", params);
Document metaDoc = DOMParser.toDocument(result.toString());
MetaData meta = new MetaData(id);
meta.streamFromXML(metaDoc.getDocumentElement(), true);
return meta;
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
public void setMetaData(String id, MetaData meta) throws XMLDBException {
checkOpen();
try {
Hashtable params = new Hashtable();
params.put(RPCDefaultMessage.COLLECTION, collPath);
if (id != null) {
params.put(RPCDefaultMessage.NAME, id);
}
params.put(RPCDefaultMessage.COMPRESSED, "true");
Document doc = new DocumentImpl();
doc.appendChild(meta.streamToXML(doc, true));
params.put(RPCDefaultMessage.META, TextWriter.toString(doc));
// Object result =
runRemoteCommand(id == null ? "SetCollectionMeta" : "SetDocumentMeta", params);
// Document metaDoc = DOMParser.toDocument(result.toString());
// meta = new MetaData(id);
// meta.streamFromXML(metaDoc.getDocumentElement(), true);
// return meta;
} catch (Exception e) {
throw FaultCodes.createXMLDBException(e);
}
}
}