/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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 edu.wisc.hrs.dao;
import java.util.Iterator;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.ws.client.WebServiceFaultException;
import org.springframework.ws.client.core.WebServiceOperations;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.SoapFaultDetailElement;
import org.springframework.ws.soap.client.SoapFaultClientException;
import org.w3c.dom.Node;
import edu.wisc.ws.client.support.SetSoapActionCallback;
/**
* Base class for HRS Web Services. Provides common invocation and error handling logic
*
* @author Eric Dalquist
* @version $Revision: 1.2 $
*/
public abstract class BaseHrsSoapDao {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private SetSoapActionCallback soapActionCallback;
@Required
public void setSoapAction(String soapAction) {
this.soapActionCallback = new SetSoapActionCallback(soapAction);
}
protected abstract WebServiceOperations getWebServiceOperations();
/**
* Calls {@link WebServiceTemplate#marshalSendAndReceive(Object)} on the passed
* in argument, and returns the result.
*
* In the event a SOAP Fault occurs (e.g. {@link SoapFaultClientException}, this method
* inspects the faultStringOrReason field.
* Based on the contents of that field, this method may throw a {@link ClientRuntimeException} or
* return null.
*
* null return values are used as hints to callers that they should throw either the checked
* {@link ClassNotFoundException}.
*
* @param request
* @return the result of {@link WebServiceTemplate#marshalSendAndReceive(Object)}, or null if the caller should throw a {@link ClassNotFoundException}
* @throws AuthenticationFailedException if the username/password configured on the webServiceTemplate are incorrect
* @throws AuthorizationFailedException if the user is authenticated but not authorized to call the particular method
* @throws WebServiceFaultException for other soap faults
*/
@SuppressWarnings("unchecked")
protected <T> T internalInvoke(Object request) {
final WebServiceOperations webServiceOperations = this.getWebServiceOperations();
try {
return (T)webServiceOperations.marshalSendAndReceive(request, this.soapActionCallback);
}
catch (SoapFaultClientException e) {
final StringBuilder errorMessage = new StringBuilder("Failed to call ");
errorMessage.append(this.soapActionCallback.getSoapAction());
errorMessage.append(" with ");
errorMessage.append(request);
final SoapFault soapFault = e.getSoapFault();
final SoapFaultDetail faultDetail = soapFault.getFaultDetail();
for (final Iterator<SoapFaultDetailElement> detailEntryItr = faultDetail.getDetailEntries(); detailEntryItr.hasNext();) {
final SoapFaultDetailElement detailEntry = detailEntryItr.next();
final Source source = detailEntry.getSource();
// TODO Once PS adds a namespace declaration to the fault document unmarshaller can be used to pull out the error message
// final Object result = this.unmarshaller.unmarshal(source);
final Node node = this.getNode(source);
if (node == null) {
//Couldn't turn the source into a node, no detailed error message for us :(
continue;
}
final String defaultTitle = evaluateXPath(node, "DefaultTitle");
final String defaultMessage = evaluateXPath(node, "DefaultMessage");
errorMessage.append("\n\t").append(defaultTitle).append(" - ").append(defaultMessage);
}
final WebServiceFaultException webServiceFaultException = new WebServiceFaultException(errorMessage.toString());
webServiceFaultException.initCause(e);
throw webServiceFaultException;
}
}
protected Node getNode(Source source) {
if (source instanceof DOMSource) {
return ((DOMSource)source).getNode();
}
//Not a DOM source, transform into a DOM node
try {
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
final DOMResult domResult = new DOMResult();
transformer.transform(source, domResult);
return domResult.getNode();
}
catch (TransformerConfigurationException tce) {
//ignore this SoapFaultDetailElement since sadly it could not be converted to a DOM Node
return null;
}
catch (TransformerException te) {
//ignore this SoapFaultDetailElement since sadly it could not be converted to a DOM Node
return null;
}
}
protected String evaluateXPath(Node node, String expression) {
final XPathFactory xpathFactory = XPathFactory.newInstance();
final XPath xPath = xpathFactory.newXPath();
final XPathExpression xpathExpression;
try {
xpathExpression = xPath.compile(expression);
}
catch (XPathExpressionException e) {
return null;
}
try {
return xpathExpression.evaluate(node);
}
catch (XPathExpressionException e) {
return null;
}
}
}