/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 org.apache.axis2.handlers.addressing; import org.apache.axiom.om.OMAttribute; import org.apache.axiom.om.OMElement; import org.apache.axiom.soap.RolePlayer; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPHeader; import org.apache.axiom.soap.SOAPHeaderBlock; import org.apache.axis2.AxisFault; import org.apache.axis2.Constants; import org.apache.axis2.addressing.AddressingConstants; import org.apache.axis2.addressing.AddressingFaultsHelper; import org.apache.axis2.addressing.EndpointReference; import org.apache.axis2.addressing.EndpointReferenceHelper; import org.apache.axis2.addressing.RelatesTo; import org.apache.axis2.client.Options; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.HandlerDescription; import org.apache.axis2.description.Parameter; import org.apache.axis2.engine.AxisConfiguration; import org.apache.axis2.engine.Handler.InvocationResponse; import org.apache.axis2.handlers.AbstractHandler; import org.apache.axis2.handlers.AbstractTemplatedHandler; import org.apache.axis2.util.JavaUtils; import org.apache.axis2.util.LoggingControl; import org.apache.axis2.util.Utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Iterator; public class AddressingInHandler extends AbstractTemplatedHandler implements AddressingConstants { private static final int TO_FLAG = 1, FROM_FLAG = 2, REPLYTO_FLAG = 3, FAULTO_FLAG = 4, MESSAGEID_FLAG = 6, ACTION_FLAG = 0; private static final Log log = LogFactory.getLog(AddressingInHandler.class); private boolean disableRefparamExtract = false; private AxisConfiguration configuration = null; private RolePlayer rolePlayer = null; public void init(HandlerDescription handlerdesc){ super.init(handlerdesc); // check whether to process reference parameters. Parameter param = handlerdesc.getParameter(DISABLE_REF_PARAMETER_EXTRACT); String value = Utils.getParameterValue(param); disableRefparamExtract = JavaUtils.isTrueExplicitly(value); if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) { log.debug("disableRefparamExtract=" + disableRefparamExtract); } } public boolean shouldInvoke(MessageContext msgContext) throws AxisFault { //Set the defaults on the message context. msgContext.setProperty(DISABLE_ADDRESSING_FOR_OUT_MESSAGES, Boolean.TRUE); msgContext.setProperty(IS_ADDR_INFO_ALREADY_PROCESSED, Boolean.FALSE); //Determine if we want to ignore addressing headers. This parameter must //be retrieved from the message context because it's value can vary on a //per service basis. Parameter disableParam = msgContext.getParameter(DISABLE_ADDRESSING_FOR_IN_MESSAGES); String value = Utils.getParameterValue(disableParam); if (JavaUtils.isTrueExplicitly(value)) { if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) { log.debug( "The AddressingInHandler has been disabled. No further processing will take place."); } return false; } // if there are not headers put a flag to disable addressing temporary SOAPHeader header = msgContext.getEnvelope().getHeader(); return header != null; } public InvocationResponse doInvoke(MessageContext msgContext) throws AxisFault { SOAPHeader header = msgContext.getEnvelope().getHeader(); if(configuration == null){ AxisConfiguration conf = msgContext.getConfigurationContext().getAxisConfiguration(); rolePlayer = (RolePlayer)conf.getParameterValue(Constants.SOAP_ROLE_PLAYER_PARAMETER); configuration = conf; } // check whether another handler has explicitly set which addressing namespace to expect. Iterator iterator = null; String namespace = (String) msgContext.getProperty(WS_ADDRESSING_VERSION); // check whether the service is configured to use a particular version of WS-Addressing, // e.g. via JAX-WS annotations. if (namespace == null) { Parameter namespaceParam = msgContext.getParameter(WS_ADDRESSING_VERSION); namespace = Utils.getParameterValue(namespaceParam); } if (namespace == null) { namespace = Final.WSA_NAMESPACE; iterator = header.getHeadersToProcess(rolePlayer, namespace); if (!iterator.hasNext()) { if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) { log.debug("No headers present corresponding to " + namespace); } namespace = Submission.WSA_NAMESPACE; iterator = header.getHeadersToProcess(rolePlayer, namespace); } } else if (Final.WSA_NAMESPACE.equals(namespace) || Submission.WSA_NAMESPACE.equals(namespace)) { iterator = header.getHeadersToProcess(rolePlayer, namespace); if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) { log.debug("The preconfigured namespace is, , " + namespace); } } else { if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) { log.debug("The specified namespace is not supported by this handler, " + namespace); } return InvocationResponse.CONTINUE; } if (iterator.hasNext()) { if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) { log.debug(namespace + " headers present in the SOAP message. Starting to process ..."); } //Need to set these properties here, before we extract the WS-Addressing //information, in case we throw a fault. msgContext.setProperty(WS_ADDRESSING_VERSION, namespace); msgContext.setProperty(DISABLE_ADDRESSING_FOR_OUT_MESSAGES, Boolean.FALSE); extractAddressingInformation(msgContext, iterator, namespace); // check for reference parameters if (!disableRefparamExtract) { extractToEprReferenceParameters(msgContext.getTo(), header, namespace); } //We should only get to this point if we haven't thrown a fault. msgContext.setProperty(IS_ADDR_INFO_ALREADY_PROCESSED, Boolean.TRUE); } else { if (LoggingControl.debugLoggingAllowed && log.isDebugEnabled()) { log.debug("No headers present corresponding to " + namespace); } } return InvocationResponse.CONTINUE; } /** * Pull addressing headers out from the SOAP message. * * @param messageContext the active MessageContext * @param headers an Iterator over the addressing headers targeted to me * @param namespace the addressing namespace * @throws AxisFault if an error occurs */ private void extractAddressingInformation(MessageContext messageContext, Iterator headers, String namespace) throws AxisFault { Options messageContextOptions = messageContext.getOptions(); ArrayList duplicateHeaderNames = new ArrayList(1); // Normally will not be used for more than 1 header ArrayList relatesToHeaders = null; SOAPHeaderBlock actionBlock = null, toBlock = null, messageIDBlock = null, replyToBlock = null, faultToBlock = null, fromBlock = null; // Per the SOAP Binding spec "headers with an incorrect cardinality MUST NOT be used" So these variables // are used to keep track of invalid cardinality headers so they are not deserialised. boolean[] ignoreHeaders = new boolean[7]; boolean[] checkedHeaderNames = new boolean[7]; // First pass just check for duplicates while (headers.hasNext()) { SOAPHeaderBlock soapHeaderBlock = (SOAPHeaderBlock)headers.next(); String localName = soapHeaderBlock.getLocalName(); if (WSA_ACTION.equals(localName)) { actionBlock = soapHeaderBlock; checkDuplicateHeaders(WSA_ACTION, ACTION_FLAG, checkedHeaderNames, ignoreHeaders, duplicateHeaderNames); } else if (WSA_TO.equals(localName)) { toBlock = soapHeaderBlock; checkDuplicateHeaders(WSA_TO, TO_FLAG, checkedHeaderNames, ignoreHeaders, duplicateHeaderNames); } else if (WSA_MESSAGE_ID.equals(localName)) { messageIDBlock = soapHeaderBlock; checkDuplicateHeaders(WSA_MESSAGE_ID, MESSAGEID_FLAG, checkedHeaderNames, ignoreHeaders, duplicateHeaderNames); } else if (WSA_REPLY_TO.equals(localName)) { replyToBlock = soapHeaderBlock; checkDuplicateHeaders(WSA_REPLY_TO, REPLYTO_FLAG, checkedHeaderNames, ignoreHeaders, duplicateHeaderNames); } else if (WSA_FAULT_TO.equals(localName)) { faultToBlock = soapHeaderBlock; checkDuplicateHeaders(WSA_FAULT_TO, FAULTO_FLAG, checkedHeaderNames, ignoreHeaders, duplicateHeaderNames); } else if (WSA_FROM.equals(localName)) { fromBlock = soapHeaderBlock; checkDuplicateHeaders(WSA_FROM, FROM_FLAG, checkedHeaderNames, ignoreHeaders, duplicateHeaderNames); } else if (WSA_RELATES_TO.equals(localName)) { if (relatesToHeaders == null) { relatesToHeaders = new ArrayList(1); } relatesToHeaders.add(soapHeaderBlock); } } if (actionBlock != null && !ignoreHeaders[ACTION_FLAG]) { extractActionInformation(actionBlock, messageContext); } if (toBlock != null && !ignoreHeaders[TO_FLAG]) { extractToEPRInformation(toBlock, messageContextOptions, namespace); } if (messageIDBlock != null && !ignoreHeaders[MESSAGEID_FLAG]) { extractMessageIDInformation(messageIDBlock, messageContext); } if (relatesToHeaders != null) { for (int i = 0; i < relatesToHeaders.size(); i++) { extractRelatesToInformation((SOAPHeaderBlock) relatesToHeaders.get(i), messageContextOptions); } } if (replyToBlock != null && !ignoreHeaders[REPLYTO_FLAG]) { extractReplyToEPRInformation(replyToBlock, namespace, messageContext); } if (faultToBlock != null && !ignoreHeaders[FAULTO_FLAG]) { extractFaultToEPRInformation(faultToBlock, namespace, messageContext); } if (fromBlock != null && !ignoreHeaders[FROM_FLAG]) { extractFromEPRInformation(fromBlock, namespace, messageContext); } // Now that all the valid wsa headers have been read, throw an exception if there was an invalid cardinality // This means that if for example there are multiple MessageIDs and a FaultTo, the FaultTo will be respected. if (!duplicateHeaderNames.isEmpty()) { if (log.isDebugEnabled()) { log.debug("Duplicate header names found:" + duplicateHeaderNames.get(0)); } // Simply choose the first problem header we came across as we can only fault for one of them. AddressingFaultsHelper.triggerInvalidCardinalityFault(messageContext, (String) duplicateHeaderNames .get(0)); } // check for the presence of madatory addressing headers checkForMandatoryHeaders(checkedHeaderNames, messageContext, namespace); // provide default values for headers that have not been found. setDefaults(checkedHeaderNames, messageContext, namespace); } private void checkForMandatoryHeaders(boolean[] alreadyFoundAddrHeader, MessageContext messageContext, String namespace) throws AxisFault { if (Final.WSA_NAMESPACE.equals(namespace)) { //Unable to validate the wsa:MessageID header here as we do not yet know which MEP //is in effect. // @see AddressingValidationHandler#checkMessageIDHeader if (!alreadyFoundAddrHeader[ACTION_FLAG]) { AddressingFaultsHelper .triggerMessageAddressingRequiredFault(messageContext, WSA_ACTION); } } else { if (!alreadyFoundAddrHeader[TO_FLAG]) { AddressingFaultsHelper.triggerMessageAddressingRequiredFault(messageContext, WSA_TO); } if (!alreadyFoundAddrHeader[ACTION_FLAG]) { AddressingFaultsHelper .triggerMessageAddressingRequiredFault(messageContext, WSA_ACTION); } if (alreadyFoundAddrHeader[REPLYTO_FLAG] || alreadyFoundAddrHeader[FAULTO_FLAG]) { if (!alreadyFoundAddrHeader[MESSAGEID_FLAG]) { AddressingFaultsHelper .triggerMessageAddressingRequiredFault(messageContext, WSA_MESSAGE_ID); } } } } private void setDefaults(boolean[] alreadyFoundAddrHeader, MessageContext messageContext, String namespace) { if (Final.WSA_NAMESPACE.equals(namespace)) { //According to the WS-Addressing spec, we should default the wsa:To header to the //anonymous URI. Doing that, however, might prevent a different value from being //used instead, such as the transport URL. Therefore, we only apply the default //on the inbound response side of a synchronous request-response exchange. if (!alreadyFoundAddrHeader[TO_FLAG] && !messageContext.isServerSide()) { if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace(messageContext.getLogIDString() + " setDefaults: Setting WS-Addressing default value for the To property."); } messageContext.setTo(new EndpointReference(Final.WSA_ANONYMOUS_URL)); } if (!alreadyFoundAddrHeader[REPLYTO_FLAG]) { messageContext.setReplyTo(new EndpointReference(Final.WSA_ANONYMOUS_URL)); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace(messageContext.getLogIDString() + " setDefaults: Setting WS-Addressing default value for the ReplyTo property."); } } } else { //The none URI is not defined in the 2004/08 spec, but it is used here anyway //as a flag to indicate the correct semantics to apply, i.e. in the 2004/08 spec //the absence of a ReplyTo header indicates that a response is NOT required. if (!alreadyFoundAddrHeader[REPLYTO_FLAG]) { messageContext.setReplyTo(new EndpointReference(Final.WSA_NONE_URI)); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace( "setDefaults: Setting WS-Addressing default value for the ReplyTo property."); } } } } private void checkDuplicateHeaders(String addressingHeaderName, int headerFlag, boolean[] checkedHeaderNames, boolean[] ignoreHeaders, ArrayList duplicateHeaderNames) {//throws AxisFault { // If the header name has been seen before then we should return true and add it to the list // of duplicate header names. Otherwise it is the first time we've seen the header so add it // to the checked liat and return false. ignoreHeaders[headerFlag] = checkedHeaderNames[headerFlag]; if (ignoreHeaders[headerFlag]) { duplicateHeaderNames.add(addressingHeaderName); } else { checkedHeaderNames[headerFlag] = true; } if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("checkDuplicateHeaders: addressingHeaderName=" + addressingHeaderName + " isDuplicate=" + ignoreHeaders[headerFlag]); } } private void extractRelatesToInformation(SOAPHeaderBlock soapHeaderBlock, Options messageContextOptions) { String address = soapHeaderBlock.getText(); // Extract the RelationshipType attribute if it exists OMAttribute relationshipType = soapHeaderBlock.getAttribute( new QName(AddressingConstants.WSA_RELATES_TO_RELATIONSHIP_TYPE)); String relationshipTypeString = relationshipType == null ? null : relationshipType.getAttributeValue(); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractRelatesToInformation: Extracted Relationship. Value=" + address + " RelationshipType=" + relationshipTypeString); } RelatesTo relatesTo = new RelatesTo(address, relationshipTypeString); ArrayList attributes = extractAttributesFromSOAPHeaderBlock(soapHeaderBlock); relatesTo.setExtensibilityAttributes(attributes); messageContextOptions.addRelatesTo(relatesTo); // Completed processing of this header soapHeaderBlock.setProcessed(); } private void extractFaultToEPRInformation(SOAPHeaderBlock soapHeaderBlock, String addressingNamespace, MessageContext messageContext) throws AxisFault { Options messageContextOptions = messageContext.getOptions(); EndpointReference epr = messageContextOptions.getFaultTo(); if (epr == null) { epr = new EndpointReference(""); messageContextOptions.setFaultTo(epr); } extractEPRInformation(soapHeaderBlock, epr, addressingNamespace, messageContext); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractFaultToEPRInformation: Extracted FaultTo EPR: " + epr); } soapHeaderBlock.setProcessed(); } private void extractReplyToEPRInformation(SOAPHeaderBlock soapHeaderBlock, String addressingNamespace, MessageContext messageContext) throws AxisFault { Options messageContextOptions = messageContext.getOptions(); EndpointReference epr = messageContextOptions.getReplyTo(); if (epr == null) { epr = new EndpointReference(""); messageContextOptions.setReplyTo(epr); } extractEPRInformation(soapHeaderBlock, epr, addressingNamespace, messageContext); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractReplyToEPRInformation: Extracted ReplyTo EPR: " + epr); } soapHeaderBlock.setProcessed(); } private void extractFromEPRInformation(SOAPHeaderBlock soapHeaderBlock, String addressingNamespace, MessageContext messageContext) throws AxisFault { Options messageContextOptions = messageContext.getOptions(); EndpointReference epr = messageContextOptions.getFrom(); if (epr == null) { epr = new EndpointReference( ""); // I don't know the address now. Let me pass the empty string now and fill this // once I process the Elements under this. messageContextOptions.setFrom(epr); } extractEPRInformation(soapHeaderBlock, epr, addressingNamespace, messageContext); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractFromEPRInformation: Extracted From EPR: " + epr); } soapHeaderBlock.setProcessed(); } private void extractToEPRInformation(SOAPHeaderBlock soapHeaderBlock, Options messageContextOptions, String namespace) { EndpointReference epr; //here the addressing epr overidde what ever already there in the message context epr = new EndpointReference(soapHeaderBlock.getText()); messageContextOptions.setTo(epr); // check for address attributes Iterator addressAttributes = soapHeaderBlock.getAllAttributes(); if (addressAttributes != null && addressAttributes.hasNext()) { ArrayList attributes = new ArrayList(); while (addressAttributes.hasNext()) { OMAttribute attr = (OMAttribute) addressAttributes.next(); attributes.add(attr); } epr.setAddressAttributes(attributes); } soapHeaderBlock.setProcessed(); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractToEPRInformation: Extracted To EPR: " + epr); } } private void extractToEprReferenceParameters(EndpointReference toEPR, SOAPHeader header, String namespace) { if (Final.WSA_NAMESPACE.equals(namespace)) { Iterator headerBlocks = header.getChildElements(); while (headerBlocks.hasNext()) { OMElement headerElement = (OMElement)headerBlocks.next(); OMAttribute isRefParamAttr = headerElement.getAttribute(new QName(namespace, "IsReferenceParameter")); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractToEprReferenceParameters: Checking header: " + headerElement.getQName()); } if (isRefParamAttr != null && "true".equals(isRefParamAttr.getAttributeValue())) { toEPR.addReferenceParameter(headerElement); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractToEprReferenceParameters: Header: " + headerElement.getQName() + " has IsReferenceParameter attribute. Adding to toEPR."); } } } } else { // there is no exact way to identify ref parameters for Submission version. So let's have a handler // at the end of the flow, which puts all the handlers (which are of course mustUnderstand=false) // as reference parameters // TODO : Chinthaka } } //We assume that any action that already exists in the message context must be the //soapaction. We compare that action to the WS-Addressing action, and if they are //different we throw a fault. private void extractActionInformation(SOAPHeaderBlock soapHeaderBlock, MessageContext messageContext) throws AxisFault { Options messageContextOptions = messageContext.getOptions(); String soapAction = messageContextOptions.getAction(); String wsaAction = soapHeaderBlock.getText(); if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace("extractActionInformation: HTTP soapAction or action ='" + soapAction + "' wsa:Action='" + wsaAction + "'"); } // Need to validate that the content of the wsa:Action header is not null or whitespace if ((wsaAction == null) || "".equals(wsaAction.trim())) { if (log.isDebugEnabled()) { log.debug("The wsa:Action header is present but its contents are empty. This violates " + "rules in the WS-A specification. The SOAP node that sent this message must be changed."); } AddressingFaultsHelper.triggerActionNotSupportedFault(messageContext, wsaAction); } // The isServerSide check is because the underlying Options object is // shared between request and response MessageContexts for Sync // invocations. If the soapAction is set outbound and a wsa:Action is // received on the response they will differ (because there is no // SOAPAction header on an HTTP response). In this case we should not // check that soapAction==wsa:Action if (soapAction != null && !"".equals(soapAction) && messageContext.isServerSide()) { if (!soapAction.equals(wsaAction)) { if (log.isDebugEnabled()) { log.debug("The wsa:Action header is (" + wsaAction + ") which conflicts with the HTTP soapAction or action " + "(" + soapAction + "). This is a violation of the WS-A specification. The SOAP node that sent this message " + " must be changed."); } AddressingFaultsHelper.triggerActionMismatchFault(messageContext, soapAction, wsaAction); } } else { messageContextOptions.setAction(wsaAction); } ArrayList attributes = extractAttributesFromSOAPHeaderBlock(soapHeaderBlock); if (attributes != null) { messageContext.setProperty(AddressingConstants.ACTION_ATTRIBUTES, attributes); } soapHeaderBlock.setProcessed(); } private void extractMessageIDInformation(SOAPHeaderBlock soapHeaderBlock, MessageContext messageContext) throws AxisFault { messageContext.getOptions().setMessageId(soapHeaderBlock.getText()); ArrayList attributes = extractAttributesFromSOAPHeaderBlock(soapHeaderBlock); if (attributes != null) { messageContext.setProperty(AddressingConstants.MESSAGEID_ATTRIBUTES, attributes); } soapHeaderBlock.setProcessed(); } /** * Given the soap header block, this should extract the information within EPR. * * @param headerBlock a SOAP header which is of type EndpointReference * @param epr the EndpointReference to fill in with the extracted data * @param addressingNamespace the WSA namespace URI * @param messageContext the active MessageContext * @throws AxisFault if there is a problem */ private void extractEPRInformation(SOAPHeaderBlock headerBlock, EndpointReference epr, String addressingNamespace, MessageContext messageContext) throws AxisFault { String namespace = null; try { namespace = EndpointReferenceHelper.fromOM(epr, headerBlock); } catch (AxisFault af) { if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace( "extractEPRInformation: Exception occurred deserialising an EndpointReference.", af); } AddressingFaultsHelper .triggerMissingAddressInEPRFault(messageContext, headerBlock.getLocalName()); } //Check that the EPR has the correct namespace. if (!namespace.equals(addressingNamespace)) { if (LoggingControl.debugLoggingAllowed && log.isTraceEnabled()) { log.trace( "extractEPRInformation: Addressing namespace = " + addressingNamespace + ", EPR namespace = " + namespace); } AddressingFaultsHelper .triggerInvalidEPRFault(messageContext, headerBlock.getLocalName()); } } private ArrayList extractAttributesFromSOAPHeaderBlock(SOAPHeaderBlock soapHeaderBlock) { Iterator actionAttributes = soapHeaderBlock.getAllAttributes(); if (actionAttributes != null && actionAttributes.hasNext()) { ArrayList attributes = new ArrayList(); while (actionAttributes.hasNext()) { OMAttribute attr = (OMAttribute) actionAttributes.next(); attributes.add(attr); } return attributes; } return null; } }