/*
* 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.Writer;
import java.util.*;
import org.apache.xmlrpc.XmlRpcException;
import org.exist.source.Source;
import org.exist.xmlrpc.RpcAPI;
import org.exist.xquery.XPathException;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.CompiledExpression;
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.XMLConstants;
import static java.nio.charset.StandardCharsets.UTF_8;
public class RemoteXPathQueryService extends AbstractRemote implements XPathQueryServiceImpl, XQueryService {
private final Map<String, String> namespaceMappings = new HashMap<>();
private final Map<String, Object> variableDecls = new HashMap<>();
private final Properties outputProperties;
private String moduleLoadPath = null;
private boolean protectedMode = false;
/**
* Creates a new <code>RemoteXPathQueryService</code> instance.
*
* @param collection a <code>RemoteCollection</code> value
*/
public RemoteXPathQueryService(final RemoteCollection collection) {
super(collection);
this.outputProperties = collection.getProperties();
}
@Override
public String getName() throws XMLDBException {
return "XPathQueryService";
}
@Override
public String getVersion() throws XMLDBException {
return "1.0";
}
@Override
public ResourceSet query(final String query) throws XMLDBException {
return query(query, null);
}
@Override
public ResourceSet query(final String query, final String sortExpr)
throws XMLDBException {
try {
final Map<String, Object> optParams = new HashMap<>();
if (sortExpr != null) {
optParams.put(RpcAPI.SORT_EXPR, sortExpr);
}
if (namespaceMappings.size() > 0) {
optParams.put(RpcAPI.NAMESPACES, namespaceMappings);
}
if (variableDecls.size() > 0) {
optParams.put(RpcAPI.VARIABLES, variableDecls);
}
optParams.put(RpcAPI.BASE_URI, outputProperties.getProperty(RpcAPI.BASE_URI, collection.getPath()));
if (moduleLoadPath != null) {
optParams.put(RpcAPI.MODULE_LOAD_PATH, moduleLoadPath);
}
if (protectedMode) {
optParams.put(RpcAPI.PROTECTED_MODE, collection.getPath());
}
final List<Object> params = new ArrayList<>();
params.add(query.getBytes(UTF_8));
params.add(optParams);
final Map result = (Map) collection.getClient().execute("queryP", params);
if (result.get(RpcAPI.ERROR) != null) {
throwException(result);
}
final Object[] resources = (Object[]) result.get("results");
int handle = -1;
int hash = -1;
if (resources != null && resources.length > 0) {
handle = (Integer) result.get("id");
hash = (Integer) result.get("hash");
}
return new RemoteResourceSet(collection, outputProperties, resources, handle, hash);
} catch (final XmlRpcException xre) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, xre.getMessage(), xre);
}
}
@Override
public CompiledExpression compile(final String query) throws XMLDBException {
try {
return compileAndCheck(query);
} catch (final XPathException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
}
}
@Override
public CompiledExpression compileAndCheck(final String query) throws XMLDBException, XPathException {
try {
final Map<String, Object> optParams = new HashMap<>();
if (namespaceMappings.size() > 0) {
optParams.put(RpcAPI.NAMESPACES, namespaceMappings);
}
if (variableDecls.size() > 0) {
optParams.put(RpcAPI.VARIABLES, variableDecls);
}
if (moduleLoadPath != null) {
optParams.put(RpcAPI.MODULE_LOAD_PATH, moduleLoadPath);
}
optParams.put(RpcAPI.BASE_URI,
outputProperties.getProperty(RpcAPI.BASE_URI, collection.getPath()));
final List<Object> params = new ArrayList<>();
params.add(query.getBytes(UTF_8));
params.add(optParams);
final Map result = (Map) collection.getClient().execute("compile", params);
if (result.get(RpcAPI.ERROR) != null) {
throwXPathException(result);
}
return new RemoteCompiledExpression(query);
} catch (final XmlRpcException xre) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, xre.getMessage(), xre);
}
}
private void throwException(final Map result) throws XMLDBException {
final String message = (String) result.get(RpcAPI.ERROR);
final Integer lineInt = (Integer) result.get(RpcAPI.LINE);
final Integer columnInt = (Integer) result.get(RpcAPI.COLUMN);
final int line = lineInt == null ? 0 : lineInt.intValue();
final int column = columnInt == null ? 0 : columnInt.intValue();
final XPathException cause = new XPathException(line, column, message);
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, message, cause);
}
private void throwXPathException(final Map result) throws XPathException {
final String message = (String) result.get(RpcAPI.ERROR);
final Integer lineInt = (Integer) result.get(RpcAPI.LINE);
final Integer columnInt = (Integer) result.get(RpcAPI.COLUMN);
final int line = lineInt == null ? 0 : lineInt.intValue();
final int column = columnInt == null ? 0 : columnInt.intValue();
throw new XPathException(line, column, message);
}
@Override
public ResourceSet execute(final Source source) throws XMLDBException {
try {
final String xq = source.getContent();
return query(xq, null);
} catch (final IOException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
}
}
@Override
public ResourceSet executeStoredQuery(final String uri) throws XMLDBException {
final List<Object> params = new ArrayList<>();
params.add(uri);
params.add(new HashMap<String, Object>());
try {
final Map result = (Map) collection.getClient().execute("execute", params);
if (result.get(RpcAPI.ERROR) != null) {
throwException(result);
}
final Object[] resources = (Object[]) result.get("results");
int handle = -1;
int hash = -1;
if (resources != null && resources.length > 0) {
handle = (Integer) result.get("id");
hash = (Integer) result.get("hash");
}
return new RemoteResourceSet(collection, outputProperties, resources, handle, hash);
} catch (final XmlRpcException xre) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, xre.getMessage(), xre);
}
}
@Override
public ResourceSet query(final XMLResource res, final String query)
throws XMLDBException {
return query(res, query, null);
}
@Override
public ResourceSet query(final XMLResource res, final String query, final String sortExpr)
throws XMLDBException {
final RemoteXMLResource resource = (RemoteXMLResource) res;
try {
final Map<String, Object> optParams = new HashMap<>();
if (namespaceMappings.size() > 0) {
optParams.put(RpcAPI.NAMESPACES, namespaceMappings);
}
if (variableDecls.size() > 0) {
optParams.put(RpcAPI.VARIABLES, variableDecls);
}
if (sortExpr != null) {
optParams.put(RpcAPI.SORT_EXPR, sortExpr);
}
if (moduleLoadPath != null) {
optParams.put(RpcAPI.MODULE_LOAD_PATH, moduleLoadPath);
}
optParams.put(RpcAPI.BASE_URI,
outputProperties.getProperty(RpcAPI.BASE_URI, collection.getPath()));
if (protectedMode) {
optParams.put(RpcAPI.PROTECTED_MODE, collection.getPath());
}
final List<Object> params = new ArrayList<>();
params.add(query.getBytes(UTF_8));
params.add(resource.path.toString());
params.add(resource.idIsPresent() ? resource.getNodeId() : "");
params.add(optParams);
final Map result = (Map) collection.getClient().execute("queryP", params);
if (result.get(RpcAPI.ERROR) != null) {
throwException(result);
}
final Object[] resources = (Object[]) result.get("results");
int handle = -1;
int hash = -1;
if (resources != null && resources.length > 0) {
handle = (Integer) result.get("id");
hash = (Integer) result.get("hash");
}
return new RemoteResourceSet(collection, outputProperties, resources, handle, hash);
} catch (final XmlRpcException xre) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, xre.getMessage(), xre);
}
}
@Override
public ResourceSet queryResource(final String resource, final String query) throws XMLDBException {
final Resource res = collection.getResource(resource);
try {
if (res == null) {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE, "Resource " + resource + " not found");
}
if (!"XMLResource".equals(res.getResourceType())) {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE, "Resource " + resource + " is not an XML resource");
}
return query((XMLResource) res, query);
}
finally {
if (res!=null && res instanceof AbstractRemoteResource) ((AbstractRemoteResource)res).freeResources();
}
}
@Override
public void setCollection(final Collection collection) throws XMLDBException {
}
@Override
public String getProperty(final String name) throws XMLDBException {
return outputProperties.getProperty(name);
}
@Override
public void setProperty(final String property, final String value) throws XMLDBException {
outputProperties.setProperty(property, value);
}
@Override
public void clearNamespaces() throws XMLDBException {
namespaceMappings.clear();
}
@Override
public void removeNamespace(final String ns)
throws XMLDBException {
for (final Iterator<String> i = namespaceMappings.values().iterator(); i.hasNext(); ) {
if (i.next().equals(ns)) {
i.remove();
}
}
}
@Override
public void setNamespace(final String prefix, final String namespace) throws XMLDBException {
namespaceMappings.put(prefix != null ? prefix : XMLConstants.DEFAULT_NS_PREFIX, namespace);
}
@Override
public String getNamespace(final String prefix) throws XMLDBException {
return namespaceMappings.get(prefix != null ? prefix : XMLConstants.DEFAULT_NS_PREFIX);
}
@Override
public void declareVariable(final String qname, final Object initialValue) throws XMLDBException {
variableDecls.put(qname, initialValue);
}
@Override
public void clearVariables() throws XMLDBException {
variableDecls.clear();
}
@Override
public ResourceSet execute(final CompiledExpression expression) throws XMLDBException {
return query(((RemoteCompiledExpression) expression).getQuery());
}
@Override
public ResourceSet execute(final XMLResource res, final CompiledExpression expression) throws XMLDBException {
return query(res, ((RemoteCompiledExpression) expression).getQuery());
}
@Override
public void setXPathCompatibility(final boolean backwardsCompatible) {
// TODO: not passed
}
/**
* Calling this method has no effect. The server loads modules
* relative to its own context.
*
* @see org.exist.xmldb.XQueryService#setModuleLoadPath(java.lang.String)
*/
@Override
public void setModuleLoadPath(final String path) {
this.moduleLoadPath = path;
}
@Override
public void dump(final CompiledExpression expression, final Writer writer) throws XMLDBException {
final String query = ((RemoteCompiledExpression) expression).getQuery();
final Map<String, Object> optParams = new HashMap<>();
if (namespaceMappings.size() > 0) {
optParams.put(RpcAPI.NAMESPACES, namespaceMappings);
}
if (variableDecls.size() > 0) {
optParams.put(RpcAPI.VARIABLES, variableDecls);
}
optParams.put(RpcAPI.BASE_URI,
outputProperties.getProperty(RpcAPI.BASE_URI, collection.getPath()));
final List<Object> params = new ArrayList<>();
params.add(query);
params.add(optParams);
try {
final String dump = (String) collection.getClient().execute("printDiagnostics", params);
writer.write(dump);
} catch (final XmlRpcException | IOException e) {
throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e.getMessage(), e);
}
}
@Override
public void beginProtected() {
protectedMode = true;
}
@Override
public void endProtected() {
protectedMode = false;
}
}