/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. 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.
*/
package com.esri.gpt.server.csw.provider;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.DomUtil;
import com.esri.gpt.server.csw.provider.components.CswConstants;
import com.esri.gpt.server.csw.provider.components.CswNamespaces;
import com.esri.gpt.server.csw.provider.components.ICqlParser;
import com.esri.gpt.server.csw.provider.components.IFilterParser;
import com.esri.gpt.server.csw.provider.components.IOperationProvider;
import com.esri.gpt.server.csw.provider.components.IProviderFactory;
import com.esri.gpt.server.csw.provider.components.IQueryEvaluator;
import com.esri.gpt.server.csw.provider.components.IResponseGenerator;
import com.esri.gpt.server.csw.provider.components.ISortByParser;
import com.esri.gpt.server.csw.provider.components.ISupportedValues;
import com.esri.gpt.server.csw.provider.components.OperationContext;
import com.esri.gpt.server.csw.provider.components.OwsException;
import com.esri.gpt.server.csw.provider.components.ParseHelper;
import com.esri.gpt.server.csw.provider.components.QueryOptions;
import com.esri.gpt.server.csw.provider.components.ServiceProperties;
import com.esri.gpt.server.csw.provider.components.SupportedValues;
import com.esri.gpt.server.csw.provider.components.ValidationHelper;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Provides the CSW GetRecords operation.
*/
public class GetRecordsProvider implements IOperationProvider {
/** class variables ========================================================= */
/** The Logger. */
private static Logger LOGGER = Logger.getLogger(GetRecordsProvider.class.getName());
/** constructors ============================================================ */
/** Default constructor */
public GetRecordsProvider() {
super();
}
/** methods ================================================================= */
/**
* Builds an ogc:Filter node from HTTP GET parameters.
* @param namespace the namespace parameter values
* @param constraintFilter the constraint parameter value
* @throws Exception if a processing exception occurs
*/
protected Node buildFilterNode(String[] namespace, String constraintFilter) throws Exception {
// TODO GetRecordsDomBuilder had a different pattern??
// parse namespaces
// pattern: namespace=xmlns(ogc=http://www.opengis.net/ogc),xmlns(gml=http://www.opengis.net/gml)...
StringBuilder nsBuffer = new StringBuilder();
boolean hasCswUri = false;
boolean hasCswPfx = false;
String cswPfx = "";
if (namespace != null) {
for (String ns: namespace) {
ns = Val.chkStr(ns);
String nsPfx = null;
String nsUri = null;
if (ns.toLowerCase().startsWith("xmlns(")) {
ns = ns.substring(6);
if (ns.toLowerCase().endsWith(")")) {
ns = ns.substring(0,ns.length() - 1);
}
ns = Val.chkStr(ns);
if (ns.length() > 0) {
String[] pair = ns.split("=");
if (pair.length == 1) {
nsUri = Val.chkStr(pair[0]);
} else if (pair.length == 2) {
nsPfx = Val.chkStr(pair[0]);
nsUri = Val.chkStr(pair[1]);
}
}
}
if ((nsUri == null) || (nsUri.length() == 0)) {
String msg = "The namespace must follow the following pattern:";
msg += " xmlns(pfx1=uri1),xmlns(pfx2=uri2),...";
throw new OwsException(OwsException.OWSCODE_InvalidParameterValue,"namespace",msg);
} else {
if (nsUri.equals("http://www.opengis.net/cat/csw/")) {
hasCswUri = true;
if ((nsPfx != null) && (nsPfx.length() > 0)) {
hasCswPfx = true;
cswPfx = nsPfx;
}
}
nsUri = Val.escapeXml(nsUri);
if ((nsPfx == null) || (nsPfx.length() == 0)) {
nsBuffer.append(" xmlns=\"").append(nsUri).append("\"");
} else {
nsBuffer.append(" xmlns:").append(nsPfx).append("=\"").append(nsUri).append("\"");
}
}
}
}
// use ogc as the default namespace if no namespace parameter was supplied
if (nsBuffer.length() == 0) {
nsBuffer.append(" xmlns=\"http://www.opengis.net/ogc\"");
}
// build the constraint XML
StringBuilder sbXml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
if (hasCswUri && hasCswPfx) {
cswPfx = cswPfx+":";
} else if (hasCswUri) {
cswPfx = "";
} else {
cswPfx = "csw:";
nsBuffer.append(" xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\"");
}
sbXml.append("\r\n<").append(cswPfx).append("Constraint");
if (nsBuffer.length() > 0) {
sbXml.append(" ").append(nsBuffer);
}
sbXml.append(">");
sbXml.append("\r\n").append(constraintFilter);
sbXml.append("\r\n</").append(cswPfx).append("Constraint>");
// make the dom, find the ogc:Filter node
try {
Document dom = DomUtil.makeDomFromString(sbXml.toString(),true);
CswNamespaces ns = new CswNamespaces();
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(ns.makeNamespaceContext());
Node ndFilter = null;
Node ndConstraint = (Node)xpath.evaluate("csw:Constraint",dom,XPathConstants.NODE);
if (ndConstraint != null) {
ndFilter = (Node)xpath.evaluate("ogc:Filter",ndConstraint,XPathConstants.NODE);;
}
if (ndFilter == null) {
String msg = "The supplied constraint was not a valid ogc:Filter.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,"constraint",msg);
} else {
return ndFilter;
}
} catch (SAXException e) {
String msg = "The supplied namespace/constraint pairs were not well-formed xml: ";
msg += " "+e.toString();
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,"constraint",msg);
}
}
/**
* Builds an ogc:SortBy node from HTTP GET parameters.
* @param sortBy the sortBy parameter values
* @throws Exception if a processing exception occurs
*/
protected Node buildSortByNode(String[] sortBy) throws Exception {
// parse sort by parameters
// pattern: sortby=property1:A,property2:D...
if (sortBy != null) {
StringBuilder sbXml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sbXml.append("\r\n<ogc:SortBy xmlns:ogc=\"http://www.opengis.net/ogc\">");
boolean hadProperty = false;
for (String param: sortBy) {
param = Val.chkStr(param);
String name = null;
String dir = null;
if (param.toLowerCase().endsWith(":a")) {
name = Val.chkStr(param.substring(0,param.length() - 2));
dir= "ASC";
} else if (param.toLowerCase().endsWith(":d")) {
name = Val.chkStr(param.substring(0,param.length() - 2));
dir = "DESC";
} else {
name = Val.chkStr(param);
}
if ((name == null) || (name.length() == 0)) {
// we'll ignore this condition without an exception
} else {
hadProperty = true;
sbXml.append("\r\n<ogc:SortProperty>");
sbXml.append("\r\n<ogc:PropertyName>").append(Val.escapeXml(name)).append("</ogc:PropertyName>");
if (dir != null) {
sbXml.append("\r\n<ogc:SortOrder>").append(Val.escapeXml(dir)).append("</ogc:SortOrder>");
}
sbXml.append("\r\n</ogc:SortProperty>");
}
}
sbXml.append("\r\n</ogc:SortBy>");
if (hadProperty) {
Document dom = DomUtil.makeDomFromString(sbXml.toString(),true);
NodeList nl = dom.getChildNodes();
for (int i=0; i<nl.getLength(); i++) {
if (nl.item(i).getNodeType() == Node.ELEMENT_NODE){
return nl.item(i);
}
}
}
}
return null;
}
/**
* Executes a parsed operation request.
* @param context the operation context
* @throws Exception if a processing exception occurs
*/
public void execute(OperationContext context) throws Exception {
// initialize
LOGGER.finer("Executing csw:GetRecords request...");
IProviderFactory factory = context.getProviderFactory();
// evaluate the query
IQueryEvaluator evaluator = factory.makeQueryEvaluator(context);
if (evaluator == null) {
String msg = "IProviderFactory.makeQueryEvaluator: instantiation failed.";
LOGGER.log(Level.SEVERE,msg);
throw new OwsException(msg);
} else {
evaluator.evaluateQuery(context);
}
// generate the response
IResponseGenerator generator = factory.makeResponseGenerator(context);
if (generator == null) {
String msg = "IProviderFactory.makeResponseGenerator: instantiation failed.";
LOGGER.log(Level.SEVERE,msg);
throw new OwsException(msg);
} else {
generator.generateResponse(context);
}
}
/**
* Handles a URL based request (HTTP GET).
* @param context the operation context
* @param request the HTTP request
* @throws Exception if a processing exception occurs
*/
public void handleGet(OperationContext context, HttpServletRequest request)
throws Exception {
// initialize
LOGGER.finer("Handling csw:GetRecords request URL...");
QueryOptions qOptions = context.getRequestOptions().getQueryOptions();
ServiceProperties svcProps = context.getServiceProperties();
ParseHelper pHelper = new ParseHelper();
ValidationHelper vHelper = new ValidationHelper();
String locator;
String[] parsed;
ISupportedValues supported;
IProviderFactory factory = context.getProviderFactory();
CswNamespaces ns = new CswNamespaces();
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(ns.makeNamespaceContext());
// service and version are parsed by the parent RequestHandler
// TODO typeNames requestId distributedSearch hopCount responseHandler
// TODO resultype validate is not applicable for a GET request?
// output format
locator = "outputFormat";
parsed = pHelper.getParameterValues(request,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_OutputFormat);
context.getOperationResponse().setOutputFormat(
vHelper.validateValue(supported,locator,parsed,false));
// output schema
locator = "outputSchema";
parsed = pHelper.getParameterValues(request,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_OutputSchema);
qOptions.setOutputSchema(vHelper.validateValue(supported,locator,parsed,false));
// start and max records
parsed = pHelper.getParameterValues(request,"startPosition");
if ((parsed != null) && (parsed.length) > 0) {
qOptions.setStartRecord(Math.max(Val.chkInt(parsed[0],1),1));
}
parsed = pHelper.getParameterValues(request,"maxRecords");
if ((parsed != null) && (parsed.length) > 0) {
qOptions.setMaxRecords(Val.chkInt(parsed[0],10));
}
// result type
locator = "resultType";
parsed = pHelper.getParameterValues(request,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_ResultType);
qOptions.setResultType(vHelper.validateValue(supported,locator,parsed,false));
if (qOptions.getResultType() == null) {
qOptions.setResultType(CswConstants.ResultType_Hits);
}
// query type names
locator = "typeNames";
parsed = pHelper.getParameterValues(request,locator);
qOptions.setQueryTypeNames(vHelper.validateValues(locator,parsed,false));
// response element set type
locator = "ElementSetName";
parsed = pHelper.getParameterValues(request,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_ElementSetType);
qOptions.setElementSetType(vHelper.validateValue(supported,locator,parsed,false));
// response element names
if (qOptions.getElementSetType() == null) {
// TODO supported ElementNames this for GetRecordById as well?
locator = "ElementName";
parsed = pHelper.getParameterValues(request,locator,",");
supported = svcProps.getSupportedValues(CswConstants.Parameter_ElementName);
qOptions.setElementNames(vHelper.validateValues(supported,locator,parsed,false));
}
// constraint language
locator = "constraintLanguage";
parsed = pHelper.getParameterValues(request,locator);
supported = new SupportedValues("CQL_TEXT,FILTER",",");
String constraintLanguage = vHelper.validateValue(supported,locator,parsed,false);
// constraint version
locator = "constraint_language_version";
parsed = pHelper.getParameterValues(request,locator);
String constraintVersion = vHelper.validateValue(locator,parsed,false);
qOptions.setQueryConstraintVersion(constraintVersion);
// constraint text
locator = "constraint";
parsed = pHelper.getParameterValues(request,locator);
String constraint = vHelper.validateValue(locator,parsed,false);
// csw:CqlText
if ((constraintLanguage != null) && constraintLanguage.equalsIgnoreCase("CQL_TEXT")) {
String cql = Val.chkStr(constraint);
qOptions.setQueryConstraintCql(cql);
ICqlParser parser = factory.makeCqlParser(context,constraintVersion);
if (parser == null) {
String msg = "IProviderFactory.makeCqlParser: instantiation failed.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,locator,msg);
} else {
parser.parseCql(context,cql);
}
}
// ogc:Filter
if ((constraintLanguage == null) || constraintLanguage.equalsIgnoreCase("FILTER")) {
Node ndFilter = null;
IFilterParser parser = factory.makeFilterParser(context,constraintVersion);
if (parser == null) {
String msg = "IProviderFactory.makeFilterParser: instantiation failed.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,locator,msg);
}
String constraintFilter = Val.chkStr(constraint);
if (constraintFilter.length() > 0) {
String[] namespace = pHelper.getParameterValues(request,"namespace",",");
ndFilter = this.buildFilterNode(namespace,constraintFilter);
parser.parseFilter(context,ndFilter,xpath);
}
}
// ogc:SortBy
locator = "sortBy";
String[] sortBy = pHelper.getParameterValues(request,"sortBy",",");
if (sortBy != null) {
Node ndSortBy = this.buildSortByNode(sortBy);
if (ndSortBy != null) {
ISortByParser parser = factory.makeSortByParser(context);
if (parser == null) {
String msg = "IProviderFactory.makeSortByParser: instantiation failed.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,locator,msg);
} else {
parser.parseSortBy(context,ndSortBy,xpath);
}
}
}
// execute the request
this.execute(context);
}
/**
* Handles an XML based request (normally HTTP POST).
* @param context the operation context
* @param root the root node
* @param xpath an XPath to enable queries (properly configured with name spaces)
* @throws Exception if a processing exception occurs
*/
public void handleXML(OperationContext context, Node root, XPath xpath)
throws Exception {
// initialize
LOGGER.finer("Handling csw:GetRecords request XML...");
QueryOptions qOptions = context.getRequestOptions().getQueryOptions();
ServiceProperties svcProps = context.getServiceProperties();
ParseHelper pHelper = new ParseHelper();
ValidationHelper vHelper = new ValidationHelper();
String locator;
String[] parsed;
ISupportedValues supported;
IProviderFactory factory = context.getProviderFactory();
// service and version are parsed by the parent RequestHandler
// TODO requestId
// output format
locator = "@outputFormat";
parsed = pHelper.getParameterValues(root,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_OutputFormat);
context.getOperationResponse().setOutputFormat(
vHelper.validateValue(supported,locator,parsed,false));
// output schema
locator = "@outputSchema";
parsed = pHelper.getParameterValues(root,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_OutputSchema);
qOptions.setOutputSchema(vHelper.validateValue(supported,locator,parsed,false));
// start and max records
qOptions.setStartRecord(Math.max(Val.chkInt(xpath.evaluate("@startPosition",root),1),1));
qOptions.setMaxRecords(Val.chkInt(xpath.evaluate("@maxRecords",root),10));
// result type
locator = "@resultType";
parsed = pHelper.getParameterValues(root,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_ResultType);
qOptions.setResultType(vHelper.validateValue(supported,locator,parsed,false));
if (qOptions.getResultType() == null) {
qOptions.setResultType(CswConstants.ResultType_Hits);
}
// find the query node
locator = "csw:Query";
Node ndQuery = (Node)xpath.evaluate(locator,root,XPathConstants.NODE);
if (ndQuery != null) {
// query type names
locator = "csw:Query/@typeNames";
parsed = pHelper.getParameterValues(root,xpath,"@typeNames");
qOptions.setQueryTypeNames(vHelper.validateValues(locator,parsed,false));
// response element set type
locator = "csw:ElementSetName";
parsed = pHelper.getParameterValues(ndQuery,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_ElementSetType);
qOptions.setElementSetType(vHelper.validateValue(supported,locator,parsed,false));
// response element set type names
String elementSetType = qOptions.getElementSetType();
if (elementSetType != null) {
locator = "csw:ElementSetName/@typeNames";
parsed = pHelper.getParameterValues(ndQuery,xpath,locator);
qOptions.setElementSetTypeNames(vHelper.validateValues(locator,parsed,false));
}
// response element names
if (elementSetType == null) {
// TODO supported ElementNames
locator = "csw:ElementName";
parsed = pHelper.getParameterValues(ndQuery,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_ElementName);
qOptions.setElementNames(vHelper.validateValues(supported,locator,parsed,false));
}
// find the constraint node
Node ndConstraint = (Node)xpath.evaluate("csw:Constraint",ndQuery,XPathConstants.NODE);
if (ndConstraint != null) {
// constraint version
String constraintVersion = xpath.evaluate("@version",ndConstraint);
qOptions.setQueryConstraintVersion(constraintVersion);
// csw:CqlText
locator = "csw:CqlText";
Node ndCql = (Node)xpath.evaluate(locator,ndConstraint,XPathConstants.NODE);
if (ndCql != null) {
String cql = Val.chkStr(ndCql.getTextContent());
qOptions.setQueryConstraintCql(cql);
ICqlParser parser = factory.makeCqlParser(context,constraintVersion);
if (parser == null) {
String msg = "IProviderFactory.makeCqlParser: instantiation failed.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,locator,msg);
} else {
parser.parseCql(context,cql);
}
} else {
// ogc:Filter
locator = "ogc:Filter";
Node ndFilter = (Node)xpath.evaluate(locator,ndConstraint,XPathConstants.NODE);
if (ndFilter != null) {
IFilterParser parser = factory.makeFilterParser(context,constraintVersion);
if (parser == null) {
String msg = "IProviderFactory.makeFilterParser: instantiation failed.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,locator,msg);
} else {
parser.parseFilter(context,ndFilter,xpath);
}
} else {
String msg = "An OGC filter for the CSW constraint is required.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,locator,msg);
}
}
}
// ogc:SortBy
locator = "ogc:SortBy";
Node ndSortBy = (Node)xpath.evaluate(locator,ndQuery,XPathConstants.NODE);
if (ndSortBy != null) {
ISortByParser parser = factory.makeSortByParser(context);
if (parser == null) {
String msg = "IProviderFactory.makeSortByParser: instantiation failed.";
throw new OwsException(OwsException.OWSCODE_NoApplicableCode,locator,msg);
} else {
parser.parseSortBy(context,ndSortBy,xpath);
}
}
}
// execute the request
this.execute(context);
}
}