/**
* 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.cxf.binding.soap.interceptor;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.HeaderUtil;
import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.SoapVersion;
import org.apache.cxf.common.i18n.Message;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.OneWayProcessorInterceptor;
import org.apache.cxf.phase.Phase;
public class MustUnderstandInterceptor extends AbstractSoapInterceptor {
public static final String UNKNOWNS = "MustUnderstand.UNKNOWNS";
private static final Logger LOG = LogUtils.getL7dLogger(MustUnderstandInterceptor.class);
private static final ResourceBundle BUNDLE = LOG.getResourceBundle();
private MustUnderstandEndingInterceptor ending = new MustUnderstandEndingInterceptor();
public MustUnderstandInterceptor() {
super(Phase.PRE_PROTOCOL);
}
public MustUnderstandInterceptor(String phase) {
super(phase);
}
public void handleMessage(SoapMessage soapMessage) {
Set<QName> paramHeaders = HeaderUtil.getHeaderQNameInOperationParam(soapMessage);
if (soapMessage.getHeaders().isEmpty() && paramHeaders.isEmpty()) {
return;
}
SoapVersion soapVersion = soapMessage.getVersion();
Set<Header> mustUnderstandHeaders = new HashSet<>();
Set<URI> serviceRoles = new HashSet<>();
Set<QName> notUnderstandHeaders = new HashSet<>();
Set<Header> ultimateReceiverHeaders = new HashSet<>();
Set<QName> mustUnderstandQNames = new HashSet<>();
initServiceSideInfo(mustUnderstandQNames, soapMessage, serviceRoles, paramHeaders);
buildMustUnderstandHeaders(mustUnderstandHeaders, soapMessage,
serviceRoles, ultimateReceiverHeaders);
checkUnderstand(mustUnderstandHeaders, mustUnderstandQNames, notUnderstandHeaders);
if (!notUnderstandHeaders.isEmpty()) {
if (!isRequestor(soapMessage)) {
soapMessage.put(MustUnderstandInterceptor.UNKNOWNS, notUnderstandHeaders);
soapMessage.getInterceptorChain().add(ending);
} else {
throw new SoapFault(new Message("MUST_UNDERSTAND", BUNDLE, notUnderstandHeaders),
soapVersion.getMustUnderstand());
}
}
if (!ultimateReceiverHeaders.isEmpty() && !isRequestor(soapMessage)) {
checkUltimateReceiverHeaders(ultimateReceiverHeaders, mustUnderstandQNames, soapMessage);
}
}
@Override
public void handleFault(SoapMessage msg) {
Set<QName> unknowns = CastUtils.cast((Set<?>)msg.get(MustUnderstandInterceptor.UNKNOWNS));
if (msg.getExchange().getBindingOperationInfo() == null
&& unknowns != null && !unknowns.isEmpty()) {
//per jaxws spec, if there are must understands that we didn't understand, but couldn't map
//to an operation either, we need to throw the mustunderstand fault, not the one related to
//an unknown operation
msg.setContent(Exception.class, new SoapFault(new Message("MUST_UNDERSTAND", BUNDLE, unknowns),
msg.getVersion().getMustUnderstand()));
}
}
private void checkUltimateReceiverHeaders(Set<Header> ultimateReceiverHeaders,
Set<QName> mustUnderstandQNames,
SoapMessage soapMessage) {
soapMessage.getInterceptorChain()
.add(new UltimateReceiverMustUnderstandInterceptor(mustUnderstandQNames));
Object o = soapMessage.getContextualProperty("endpoint-processes-headers");
if (o == null) {
o = Collections.EMPTY_LIST;
}
Collection<Object> o2;
if (o instanceof Collection) {
o2 = CastUtils.cast((Collection<?>)o);
} else {
o2 = Collections.singleton(o);
}
for (Object obj : o2) {
QName qn;
if (obj instanceof QName) {
qn = (QName)obj;
} else {
qn = QName.valueOf((String)obj);
}
Iterator<Header> hit = ultimateReceiverHeaders.iterator();
while (hit.hasNext()) {
Header h = hit.next();
if (qn.equals(h.getName())) {
hit.remove();
}
}
}
if (!ultimateReceiverHeaders.isEmpty()) {
Set<QName> notFound = new HashSet<>();
for (Header h : ultimateReceiverHeaders) {
if (!mustUnderstandQNames.contains(h.getName())) {
notFound.add(h.getName());
}
}
if (!notFound.isEmpty()) {
// Defer throwing soap fault exception in SOAPHeaderInterceptor once the isOneway can
// be detected
soapMessage.put(MustUnderstandInterceptor.UNKNOWNS, notFound);
soapMessage.getInterceptorChain().add(ending);
}
}
}
private void initServiceSideInfo(Set<QName> mustUnderstandQNames, SoapMessage soapMessage,
Set<URI> serviceRoles, Set<QName> paramHeaders) {
if (paramHeaders != null) {
mustUnderstandQNames.addAll(paramHeaders);
}
for (Interceptor<? extends org.apache.cxf.message.Message> interceptorInstance
: soapMessage.getInterceptorChain()) {
if (interceptorInstance instanceof SoapInterceptor) {
SoapInterceptor si = (SoapInterceptor) interceptorInstance;
Set<URI> roles = si.getRoles();
if (roles != null) {
serviceRoles.addAll(roles);
}
Set<QName> understoodHeaders = si.getUnderstoodHeaders();
if (understoodHeaders != null) {
mustUnderstandQNames.addAll(understoodHeaders);
}
}
}
}
private void buildMustUnderstandHeaders(Set<Header> mustUnderstandHeaders,
SoapMessage soapMessage,
Set<URI> serviceRoles,
Set<Header> ultimateReceiverHeaders) {
for (Header header : soapMessage.getHeaders()) {
if (header instanceof SoapHeader && ((SoapHeader)header).isMustUnderstand()) {
String role = ((SoapHeader)header).getActor();
if (!StringUtils.isEmpty(role)) {
role = role.trim();
if (role.equals(soapMessage.getVersion().getNextRole())) {
mustUnderstandHeaders.add(header);
} else if (role.equals(soapMessage.getVersion().getUltimateReceiverRole())) {
ultimateReceiverHeaders.add(header);
} else {
for (URI roleFromBinding : serviceRoles) {
if (role.equals(roleFromBinding.toString())) {
mustUnderstandHeaders.add(header);
}
}
}
} else {
// if role omitted, the soap node is ultimate receiver,
// needs to understand
ultimateReceiverHeaders.add(header);
}
}
}
}
private void checkUnderstand(Set<Header> mustUnderstandHeaders,
Set<QName> mustUnderstandQNames,
Set<QName> notUnderstandHeaders) {
for (Header header : mustUnderstandHeaders) {
QName qname = header.getName();
if (!mustUnderstandQNames.contains(qname)) {
notUnderstandHeaders.add(header.getName());
}
}
}
/**
*
*/
private static class UltimateReceiverMustUnderstandInterceptor extends AbstractSoapInterceptor {
Set<QName> knownHeaders;
UltimateReceiverMustUnderstandInterceptor(Set<QName> knownHeaders) {
super(Phase.INVOKE);
this.knownHeaders = knownHeaders;
}
public void handleMessage(SoapMessage soapMessage) throws Fault {
SoapVersion soapVersion = soapMessage.getVersion();
Set<QName> notFound = new HashSet<>();
List<Header> heads = soapMessage.getHeaders();
for (Header header : heads) {
if (header instanceof SoapHeader
&& ((SoapHeader)header).isMustUnderstand()
&& header.getDirection() == Header.Direction.DIRECTION_IN
&& !knownHeaders.contains(header.getName())
&& (StringUtils.isEmpty(((SoapHeader)header).getActor())
|| soapVersion.getUltimateReceiverRole()
.equals(((SoapHeader)header).getActor()))) {
notFound.add(header.getName());
}
}
if (!notFound.isEmpty()) {
soapMessage.remove(UNKNOWNS);
throw new SoapFault(new Message("MUST_UNDERSTAND", BUNDLE, notFound),
soapVersion.getMustUnderstand());
}
}
}
public static class MustUnderstandEndingInterceptor extends AbstractSoapInterceptor {
public MustUnderstandEndingInterceptor() {
super(Phase.PRE_LOGICAL);
addAfter(OneWayProcessorInterceptor.class.getName());
}
public MustUnderstandEndingInterceptor(String phase) {
super(phase);
}
public void handleMessage(SoapMessage message) throws Fault {
// throws soapFault after the response code 202 is set in OneWayProcessorInterceptor
if (message.get(MustUnderstandInterceptor.UNKNOWNS) != null) {
//we may not have known the Operation in the main interceptor and thus may not
//have been able to get the parameter based headers. We now know the
//operation and thus can remove those.
Set<QName> unknowns = CastUtils.cast((Set<?>)message.get(MustUnderstandInterceptor.UNKNOWNS));
Set<QName> paramHeaders = HeaderUtil.getHeaderQNameInOperationParam(message);
unknowns.removeAll(paramHeaders);
message.remove(MustUnderstandInterceptor.UNKNOWNS);
if (!unknowns.isEmpty()) {
throw new SoapFault(new Message("MUST_UNDERSTAND", BUNDLE, unknowns),
message.getVersion().getMustUnderstand());
}
}
}
}
}