/* 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.components;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.security.credentials.UsernameCredential;
import com.esri.gpt.framework.security.identity.NotAuthorizedException;
import com.esri.gpt.framework.security.principal.User;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.DomUtil;
import com.esri.gpt.framework.xml.XmlIoUtil;
import com.esri.gpt.sdisuite.IntegrationContext;
import com.esri.gpt.sdisuite.IntegrationContextFactory;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
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;
/**
* Request handler.
*/
public class RequestHandler {
/** class variables ========================================================= */
/** The Logger. */
private static Logger LOGGER = Logger.getLogger(RequestHandler.class.getName());
/** instance variables ====================================================== */
private OperationContext operationContext;
private boolean wasSoap = false;
/** constructors ============================================================ */
/** Default constructor */
public RequestHandler() {}
/** properties ============================================================== */
/**
* Gets the operation context.
* @return the operation context
*/
public OperationContext getOperationContext() {
return this.operationContext;
}
/**
* Sets the operation context.
* @param context the operation context
*/
public void setOperationContext(OperationContext context) {
this.operationContext = context;
}
/**
* Gets the flag indicating a SOAP based request.
* @return <code>true</code> is this was a SOAP based request
*/
public boolean getWasSoap() {
return this.wasSoap;
}
/**
* Sets the flag indicating a SOAP based request.
* @param wasSoap <code>true</code> is this was a SOAP based request
*/
public void setWasSoap(boolean wasSoap) {
this.wasSoap = wasSoap;
}
/** methods ================================================================= */
/**
* Handles a URL based request (HTTP GET).
* @param request the HTTP request
* @throws Exception if a processing exception occurs
*/
public OperationResponse handleGet(HttpServletRequest request)
throws Exception {
// initialize
LOGGER.finer("Handling CSW request URL...");
OperationContext context = this.getOperationContext();
IProviderFactory factory = context.getProviderFactory();
ServiceProperties svcProps = context.getServiceProperties();
ParseHelper pHelper = new ParseHelper();
ValidationHelper vHelper = new ValidationHelper();
String locator;
String[] parsed;
ISupportedValues supported;
// determine the operation name and provider
String opName = null;
IOperationProvider opProvider = null;
locator = "request";
parsed = pHelper.getParameterValues(request,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_OperationName);
opName = vHelper.validateValue(supported,locator,parsed,true);
context.setOperationName(opName);
if (opName != null) {
opProvider = factory.makeOperationProvider(context,opName);
}
// ensure a valid operation provider
if (opProvider == null) {
if ((opName == null) || (opName.length() == 0)) {
throw new OwsException(OwsException.OWSCODE_MissingParameterValue,
"request","The request parameter was missing.");
} else {
throw new OwsException(OwsException.OWSCODE_OperationNotSupported,
"request","Unsupported operation: "+opName);
}
}
// parse the service and version
this.parseServiceAndVersion(context,request);
// parse and execute the operation
opProvider.handleGet(context,request);
return context.getOperationResponse();
}
/**
* Handles an XML based request (normally HTTP POST).
* @param xml the XML
* @throws Exception if a processing exception occurs
*/
public OperationResponse handleXML(String xml) throws Exception {
// initialize
LOGGER.finer("Handling CSW request XML...");
OperationContext context = this.getOperationContext();
IProviderFactory factory = context.getProviderFactory();
Node root = null;
String nsSoapUri = null;
String nsSoapPfx = null;
try {
// load the dom
context.getRequestOptions().setRequestXml(xml);
Document dom = this.loadDom(xml);
context.getRequestOptions().setRequestDom(dom);
// make an XPath for the CSW name space context
CswNamespaces ns = new CswNamespaces();
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(ns.makeNamespaceContext());
// check for a soap request, if so determine the root CSW operation node
/*
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Body xmlns:m="http://www.example.org/stock">
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
*/
String expr = "/SOAP-ENV:Envelope | /soap:Envelope | /soap_2003_05:Envelope";
Node ndSoapEnv = (Node)xpath.evaluate(expr,dom,XPathConstants.NODE);
if (ndSoapEnv != null) {
this.setWasSoap(true);
nsSoapUri = ndSoapEnv.getNamespaceURI();
nsSoapPfx = ndSoapEnv.lookupPrefix(nsSoapUri);
expr = "//SOAP-ENV:Body | //soap:Body | //soap_2003_05:Body";
Node ndSoapBody = (Node)xpath.evaluate(expr,ndSoapEnv,XPathConstants.NODE);
if (ndSoapBody == null) {
throw new OwsException(OwsException.OWSCODE_MissingParameterValue,
"Envelope","The SOAP body was missing.");
} else {
NodeList nl = ndSoapBody.getChildNodes();
for (int i=0; i<nl.getLength(); i++) {
if (nl.item(i).getNodeType() == Node.ELEMENT_NODE){
root = nl.item(i);
break;
}
}
if (root == null) {
throw new OwsException(OwsException.OWSCODE_MissingParameterValue,
"Body","No CSW operation was located within the soap body.");
}
}
// check for an sdi-suite SAML token
expr = "//tcExt:samlTicket";
Node ndSaml = (Node)xpath.evaluate(expr,ndSoapEnv,XPathConstants.NODE);
if (ndSaml != null) {
String samlToken = ndSaml.getTextContent();
if (samlToken != null) {
IntegrationContextFactory icf = new IntegrationContextFactory();
if (icf.isIntegrationEnabled()) {
IntegrationContext ic = icf.newIntegrationContext();
if (ic != null) {
String samlUsername = ic.getUsernameFromSAMLToken(samlToken);
RequestContext rc = context.getRequestContext();
if ((rc != null) && (samlUsername != null)) {
User user = rc.getUser();
user.reset();
user.setCredentials(new UsernameCredential(samlUsername));
rc.newIdentityAdapter().authenticate(user);
context.getRequestOptions().getTransactionOptions().setPublicationMethod("seditor");
expr = "//tcExt:tcSecurity/tcExt:tcSecuredAction[tcExt:status='draft']";
Node ndTmp = (Node)xpath.evaluate(expr,ndSoapEnv,XPathConstants.NODE);
if (ndTmp != null) {
context.getRequestOptions().getTransactionOptions().setApprovalStatus("draft");
}
}
}
}
}
}
}
// if this wasn't a SOAP request, determine the root node
if (!this.getWasSoap()) {
NodeList nl = dom.getChildNodes();
for (int i=0; i<nl.getLength(); i++) {
if (nl.item(i).getNodeType() == Node.ELEMENT_NODE){
root = nl.item(i);
break;
}
}
}
// determine the operation name and provider
String opName = null;
IOperationProvider opProvider = null;
String namespace = root.getNamespaceURI();
opName = root.getLocalName();
if (CswNamespaces.URI_CSW.equals(namespace)) {
context.setOperationName(opName);
if (opName != null) {
opProvider = factory.makeOperationProvider(context,opName);
}
}
// ensure a valid operation provider
if (opProvider == null) {
throw new OwsException(OwsException.OWSCODE_OperationNotSupported,
"root-node","Unsupported operation: "+opName);
}
// parse the service and version
this.parseServiceAndVersion(context,root,xpath);
// parse and execute the operation
opProvider.handleXML(context,root,xpath);
} catch (NotAuthorizedException e) {
throw e;
} catch (Exception e) {
if (this.getWasSoap()) {
this.generateSoapResponse(context,nsSoapUri,nsSoapPfx,e);
return context.getOperationResponse();
} else {
throw e;
}
}
// wrap the SOAP response if required
if (this.getWasSoap()) {
this.generateSoapResponse(context,nsSoapUri,nsSoapPfx,null);
}
return context.getOperationResponse();
}
/**
* Wraps the response within a SOAp envelope.
* <br/>If the exception argument is null, the operation response is wrapped.
* <br/>If the exception argument is not null, an OWS exception report is wrapped.
* <br/>The OperationResponse.responseXml is reset to the SOAP response string.
* @param context the operation context
* @param nsSoapUri the SOAP namespace URI
* @param nsSoapPfx the SOAP namespace prefix
* @param exception an exception condition that should be wrapped as an OWS exception report
* @throws Exception Exception if a processing exception occurs
*/
protected void generateSoapResponse(OperationContext context,
String nsSoapUri,
String nsSoapPfx,
Exception exception)
throws Exception {
// make the SOAP document
if (Val.chkStr(nsSoapPfx).length() == 0) {
nsSoapPfx = "soap";
}
Document domSoap = DomUtil.newDocument();
Node ndSoapEnv = domSoap.createElementNS(nsSoapUri,nsSoapPfx+":Envelope");
Node ndSoapBody = domSoap.createElementNS(nsSoapUri,nsSoapPfx+":Body");
domSoap.appendChild(ndSoapEnv);
ndSoapEnv.appendChild(ndSoapBody);
// check for an exception condition
OperationResponse opResponse = context.getOperationResponse();
if (exception != null) {
if (exception instanceof OwsException) {
OwsException ows = (OwsException)exception;
LOGGER.finer("Invalid CSW request: "+exception.getMessage());
opResponse.setResponseXml(ows.getReport(context));
} else {
OwsException ows = new OwsException(exception);
LOGGER.log(Level.WARNING,exception.toString(),exception);
opResponse.setResponseXml(ows.getReport(context));
}
}
try {
// wrap the response within the SOAP body
Document domCsw = DomUtil.makeDomFromString(opResponse.getResponseXml(),true);
NodeList nl = domCsw.getChildNodes();
for (int i=0; i<nl.getLength(); i++) {
if (nl.item(i).getNodeType() == Node.ELEMENT_NODE){
Node cswRoot = nl.item(i);
Node ndImported = domSoap.importNode(cswRoot,true);
ndSoapBody.appendChild(ndImported);
opResponse.setResponseXml(XmlIoUtil.domToString(domSoap));
break;
}
}
// throw a SOAP fault
} catch (Exception e) {
Node ndFault = domSoap.createElementNS(nsSoapUri,nsSoapPfx+":Fault");
Node ndCode = domSoap.createElement("faultcode");
Node ndReason = domSoap.createElement("faultstring");
Node ndDetail = domSoap.createElementNS(nsSoapUri,nsSoapPfx+":Detail");
Node ndMessage = domSoap.createElement("message");
ndFault.appendChild(ndCode);
ndCode.setTextContent("Server");
ndFault.appendChild(ndReason);
ndReason.setTextContent("An error occurred while generating the SOAP body.");
ndFault.appendChild(ndDetail);
ndDetail.appendChild(ndMessage);
ndMessage.setTextContent(e.toString());
ndSoapBody.appendChild(ndFault);
opResponse.setResponseXml(XmlIoUtil.domToString(domSoap));
}
}
/**
* Loads an XML string into an XML Document.
* @param xml the document XML string
* @return the document
* @throws OwsException if the document fails to load
*/
protected Document loadDom(String xml) throws OwsException {
String sErrorMsg = "Unable to parse incoming XML document.";
try {
xml = Val.chkStr(xml);
if (xml.length() == 0) {
sErrorMsg = "The incoming XML document was empty.";
}
return DomUtil.makeDomFromString(xml,true);
} catch (ParserConfigurationException e) {
throw new OwsException(sErrorMsg,e);
} catch (SAXException e) {
throw new OwsException(sErrorMsg,e);
} catch (IOException e) {
throw new OwsException(sErrorMsg,e);
}
}
/**
* Parses the service name and version for a URL based request (HTTP GET).
* @param context the operation context
* @param request the HTTP request
* @throws OwsException if validation fails
* @throws XPathExpressionException if an XPath related exception occurs
*/
public void parseServiceAndVersion(OperationContext context, HttpServletRequest request)
throws OwsException {
// initialize
LOGGER.finer("Parsing request URL for service and version...");
ServiceProperties svcProps = context.getServiceProperties();
ParseHelper pHelper = new ParseHelper();
ValidationHelper vHelper = new ValidationHelper();
String locator;
String[] parsed;
ISupportedValues supported;
// service name
locator = "service";
parsed = pHelper.getParameterValues(request,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_Service);
String service = vHelper.validateValue(supported,locator,parsed,true);
svcProps.setServiceName(service);
// service version
locator = "acceptVersions";
parsed = pHelper.getParameterValues(request,locator,",");
supported = svcProps.getSupportedValues(CswConstants.Parameter_Version);
String version = Val.chkStr(vHelper.negotiateValue(supported,locator,parsed,false));
if (version.length() > 0) {
svcProps.setServiceVersion(version);
} else {
locator = "version";
parsed = pHelper.getParameterValues(request,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_Version);
version = vHelper.validateValue(supported,locator,parsed,false);
svcProps.setServiceVersion(version);
}
}
/**
* Parses the service name and version 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 OwsException if validation fails
* @throws XPathExpressionException if an XPath related exception occurs
*/
public void parseServiceAndVersion(OperationContext context, Node root, XPath xpath)
throws OwsException, XPathExpressionException {
// initialize
LOGGER.finer("Parsing request XML for service and version...");
ServiceProperties svcProps = context.getServiceProperties();
ParseHelper pHelper = new ParseHelper();
ValidationHelper vHelper = new ValidationHelper();
String locator;
String[] parsed;
ISupportedValues supported;
// service name
locator = "@service";
parsed = pHelper.getParameterValues(root,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_Service);
String service = vHelper.validateValue(supported,locator,parsed,true);
svcProps.setServiceName(service);
// service version
locator = "ows:AcceptVersions/ows:Version";
parsed = pHelper.getParameterValues(root,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_Version);
String version = Val.chkStr(vHelper.negotiateValue(supported,locator,parsed,false));
if (version.length() > 0) {
svcProps.setServiceVersion(version);
} else {
locator = "@version";
parsed = pHelper.getParameterValues(root,xpath,locator);
supported = svcProps.getSupportedValues(CswConstants.Parameter_Version);
version = vHelper.validateValue(supported,locator,parsed,false);
svcProps.setServiceVersion(version);
}
}
}