/** * Copyright 2015 Nortal Licensed 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.nortal.jroad.endpoint; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.soap.AttachmentPart; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.endpoint.MessageEndpoint; import org.springframework.ws.wsdl.wsdl11.provider.SuffixBasedMessagesProvider; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.nortal.jroad.enums.XRoadProtocolVersion; import com.nortal.jroad.model.BeanXTeeMessage; import com.nortal.jroad.model.XTeeAttachment; import com.nortal.jroad.model.XTeeHeader; import com.nortal.jroad.model.XTeeMessage; import com.nortal.jroad.util.SOAPUtil; import com.nortal.jroad.wsdl.XTeeWsdlDefinition; /** * Base class for X-Tee Spring web-service endpoints, extension classes must implement * {@link AbstractXTeeBaseEndpoint#invokeInternal(XTeeMessage request, XTeeMessage response)}. * * @author Roman Tekhov * @author Dmitri Danilkin * @author Lauri Lättemäe (lauri.lattemae@nortal.com) - protocol 4.0 */ public abstract class AbstractXTeeBaseEndpoint implements MessageEndpoint { protected boolean metaService = false; protected XRoadProtocolVersion version; public final void invoke(MessageContext messageContext) throws Exception { SOAPMessage paringMessage = SOAPUtil.extractSoapMessage(messageContext.getRequest()); SOAPMessage responseMessage = SOAPUtil.extractSoapMessage(messageContext.getResponse()); version = parseProtocolVersion(paringMessage); // meta-service does not need 'header' element if (metaService) { responseMessage.getSOAPHeader().detachNode(); } Document paring = metaService ? null : parseQuery(paringMessage); getResponse(paring, responseMessage, paringMessage); } @SuppressWarnings("unchecked") protected XRoadProtocolVersion parseProtocolVersion(SOAPMessage requestMessage) throws SOAPException { XRoadProtocolVersion version = null; // Extract protocol version by headers if (requestMessage.getSOAPHeader() != null) { NodeList reqHeaders = requestMessage.getSOAPHeader().getChildNodes(); for (int i = 0; i < reqHeaders.getLength(); i++) { Node reqHeader = reqHeaders.item(i); if (reqHeader.getNodeType() != Node.ELEMENT_NODE || !reqHeader.getLocalName().equals(XTeeHeader.PROTOCOL_VERSION.getLocalPart())) { continue; } if ((version = XRoadProtocolVersion.getValueByVersionCode(SOAPUtil.getTextContent(reqHeader))) != null) { return version; } } } // Extract protocol version by namespaces SOAPEnvelope soapEnv = requestMessage.getSOAPPart().getEnvelope(); Iterator<String> prefixes = soapEnv.getNamespacePrefixes(); while (prefixes.hasNext()) { String nsPrefix = (String) prefixes.next(); String nsURI = soapEnv.getNamespaceURI(nsPrefix).toLowerCase(); if ((version = XRoadProtocolVersion.getValueByNamespaceURI(nsURI)) != null) { return version; } } throw new IllegalStateException("Unsupported protocol version"); } @SuppressWarnings("unchecked") protected void getResponse(Document query, SOAPMessage responseMessage, SOAPMessage requestMessage) throws Exception { XTeeHeader header = metaService ? null : parseXteeHeader(requestMessage); // Build request message List<XTeeAttachment> attachments = new ArrayList<XTeeAttachment>(); for (Iterator<AttachmentPart> i = requestMessage.getAttachments(); i.hasNext();) { AttachmentPart a = i.next(); attachments.add(new XTeeAttachment(a.getContentId(), a.getContentType(), a.getRawContentBytes())); } XTeeMessage<Document> request = new BeanXTeeMessage<Document>(header, query, attachments); SOAPElement teenusElement = createXteeMessageStructure(requestMessage, responseMessage); if (XRoadProtocolVersion.V2_0 == version) { if (!metaService) { copyParing(query, teenusElement); } teenusElement = teenusElement.addChildElement("keha"); } // Build response message XTeeMessage<Element> response = new BeanXTeeMessage<Element>(header, teenusElement, new ArrayList<XTeeAttachment>()); // Run logic invokeInternalEx(request, response, requestMessage, responseMessage); // Add any attachments for (XTeeAttachment a : response.getAttachments()) { AttachmentPart attachment = responseMessage.createAttachmentPart(a.getDataHandler()); attachment.setContentId("<" + a.getCid() + ">"); responseMessage.addAttachmentPart(attachment); } } @SuppressWarnings("unchecked") private XTeeHeader parseXteeHeader(SOAPMessage paringMessage) throws SOAPException { XTeeHeader pais = new XTeeHeader(); if (paringMessage.getSOAPHeader() == null) { return pais; } SOAPHeader header = paringMessage.getSOAPHeader(); for (Iterator<Node> headerElemendid = header.getChildElements(); headerElemendid.hasNext();) { Node headerElement = headerElemendid.next(); if (!SOAPUtil.isTextNode(headerElement) && headerElement.getFirstChild() != null) { String localName = headerElement.getLocalName(); String value = headerElement.getFirstChild().getNodeValue(); pais.addElement(new QName(headerElement.getNamespaceURI(), localName), value); } } return pais; } protected Document parseQuery(SOAPMessage queryMsg) throws Exception { Node bodyNode = SOAPUtil.getFirstNonTextChild(queryMsg.getSOAPBody()); if (XRoadProtocolVersion.V2_0 == version) { bodyNode = SOAPUtil.getNodeByXPath(bodyNode, "//keha"); if (bodyNode == null) { throw new IllegalStateException("Service is not metaservice, but query is missing mandatory body ('//keha\')"); } } Document query = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); bodyNode = query.importNode(bodyNode, true); query.appendChild(bodyNode); return query; } @SuppressWarnings("unchecked") protected SOAPElement createXteeMessageStructure(SOAPMessage requestMessage, SOAPMessage responseMessage) throws Exception { SOAPUtil.addBaseMimeHeaders(responseMessage); SOAPUtil.addBaseNamespaces(responseMessage); if (!metaService) { // Assign xroad namespaces according to request List<String> xteeNamespaces = new ArrayList<String>(); xteeNamespaces.add(version.getNamespaceUri()); if (XRoadProtocolVersion.V4_0 == version) { xteeNamespaces.add(XTeeWsdlDefinition.XROAD_IDEN_NAMESPACE); } Iterator<String> prefixes = requestMessage.getSOAPPart().getEnvelope().getNamespacePrefixes(); while (prefixes.hasNext()) { String nsPrefix = (String) prefixes.next(); String nsURI = requestMessage.getSOAPPart().getEnvelope().getNamespaceURI(nsPrefix).toLowerCase(); if (xteeNamespaces.contains(nsURI)) { SOAPUtil.addNamespace(responseMessage, nsPrefix, nsURI); } } // Copy headers from request NodeList reqHeaders = requestMessage.getSOAPHeader().getChildNodes(); for (int i = 0; i < reqHeaders.getLength(); i++) { Node reqHeader = reqHeaders.item(i); if (reqHeader.getNodeType() != Node.ELEMENT_NODE) { continue; } Node rspHeader = responseMessage.getSOAPPart().importNode(reqHeader, true); responseMessage.getSOAPHeader().appendChild(rspHeader); } } responseMessage.getSOAPPart().getEnvelope().setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/"); Node teenusElement = SOAPUtil.getFirstNonTextChild(requestMessage.getSOAPBody()); if (teenusElement.getPrefix() == null || teenusElement.getNamespaceURI() == null) { throw new IllegalStateException("Service request is missing namespace."); } SOAPUtil.addNamespace(responseMessage, teenusElement.getPrefix(), teenusElement.getNamespaceURI()); String teenusElementName = teenusElement.getLocalName(); if (teenusElementName.endsWith(SuffixBasedMessagesProvider.DEFAULT_REQUEST_SUFFIX)) { teenusElementName = teenusElementName.substring(0, teenusElementName.lastIndexOf(SuffixBasedMessagesProvider.DEFAULT_REQUEST_SUFFIX)); } teenusElementName += SuffixBasedMessagesProvider.DEFAULT_RESPONSE_SUFFIX; return responseMessage.getSOAPBody().addChildElement(teenusElementName, teenusElement.getPrefix(), teenusElement.getNamespaceURI()); } private void copyParing(Document paring, Node response) throws Exception { Node paringElement = response.appendChild(response.getOwnerDocument().createElement("paring")); Node kehaNode = response.getOwnerDocument().importNode(paring.getDocumentElement(), true); NamedNodeMap attrs = kehaNode.getAttributes(); for (int i = 0; i < attrs.getLength(); i++) { paringElement.getAttributes().setNamedItem(attrs.item(i).cloneNode(true)); } while (kehaNode.hasChildNodes()) { paringElement.appendChild(kehaNode.getFirstChild()); } } /** * If true, request will be processed as a meta-request (an example of a meta-query is <code>listMethods</code>). */ public void setMetaService(boolean metaService) { this.metaService = metaService; } /** Returns <code>true</code>, if this is a meta service. */ public boolean isMetaService() { return metaService; } /** * This method can be overridden if you need direct access to the request and response messages. * * @param request * @param response * @param responseMessage * @param requestMessage * @throws Exception */ protected void invokeInternalEx(XTeeMessage<Document> request, XTeeMessage<Element> response, SOAPMessage responseMessage, SOAPMessage requestMessage) throws Exception { invokeInternal(request, response); } /** * Method which must implement the service logic, receives <code>request</code> and <code>response<code>. * * @param request * @param response */ protected void invokeInternal(XTeeMessage<Document> request, XTeeMessage<Element> response) throws Exception { throw new IllegalStateException("You must override either the 'invokeInternal' or the 'invokeInternalEx' method!"); }; }