/**
* 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.io.EOFException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.validation.Schema;
import org.w3c.dom.Element;
import org.apache.cxf.Bus;
import org.apache.cxf.annotations.SchemaValidation.SchemaValidationType;
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.binding.soap.model.SoapHeaderInfo;
import org.apache.cxf.common.i18n.BundleUtils;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.databinding.DataBinding;
import org.apache.cxf.databinding.DataWriter;
import org.apache.cxf.headers.Header;
import org.apache.cxf.headers.HeaderManager;
import org.apache.cxf.headers.HeaderProcessor;
import org.apache.cxf.helpers.ServiceUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.WriteOnCloseOutputStream;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.service.Service;
import org.apache.cxf.service.model.BindingMessageInfo;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.apache.cxf.service.model.MessagePartInfo;
import org.apache.cxf.service.model.ServiceModelUtil;
import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;
import org.apache.cxf.staxutils.StaxUtils;
import org.apache.cxf.staxutils.W3CDOMStreamWriter;
import org.apache.cxf.ws.addressing.EndpointReferenceUtils;
public class SoapOutInterceptor extends AbstractSoapInterceptor {
public static final String WROTE_ENVELOPE_START = "wrote.envelope.start";
private static final ResourceBundle BUNDLE = BundleUtils.getBundle(SoapOutInterceptor.class);
private Bus bus;
public SoapOutInterceptor(Bus b) {
super(Phase.WRITE);
bus = b;
}
public SoapOutInterceptor(Bus b, String phase) {
super(phase);
bus = b;
}
public void handleMessage(SoapMessage message) {
// Yes this is ugly, but it avoids us from having to implement any kind of caching strategy
boolean wroteStart = MessageUtils.isTrue(message.get(WROTE_ENVELOPE_START));
if (!wroteStart) {
writeSoapEnvelopeStart(message);
OutputStream os = message.getContent(OutputStream.class);
// Unless we're caching the whole message in memory skip the envelope writing
// if there's a fault later.
if (!(os instanceof WriteOnCloseOutputStream) && !MessageUtils.isDOMPresent(message)) {
message.put(WROTE_ENVELOPE_START, Boolean.TRUE);
}
}
// Add a final interceptor to write end elements
message.getInterceptorChain().add(new SoapOutEndingInterceptor());
}
private void writeSoapEnvelopeStart(final SoapMessage message) {
final SoapVersion soapVersion = message.getVersion();
try {
XMLStreamWriter xtw = message.getContent(XMLStreamWriter.class);
String soapPrefix = xtw.getPrefix(soapVersion.getNamespace());
if (StringUtils.isEmpty(soapPrefix)) {
soapPrefix = "soap";
}
if (message.hasAdditionalEnvNs()) {
Map<String, String> nsMap = message.getEnvelopeNs();
for (Map.Entry<String, String> entry : nsMap.entrySet()) {
if (soapVersion.getNamespace().equals(entry.getValue())) {
soapPrefix = entry.getKey();
}
}
xtw.setPrefix(soapPrefix, soapVersion.getNamespace());
xtw.writeStartElement(soapPrefix,
soapVersion.getEnvelope().getLocalPart(),
soapVersion.getNamespace());
xtw.writeNamespace(soapPrefix, soapVersion.getNamespace());
for (Map.Entry<String, String> entry : nsMap.entrySet()) {
if (!soapVersion.getNamespace().equals(entry.getValue())) {
xtw.writeNamespace(entry.getKey(), entry.getValue());
}
}
} else {
xtw.setPrefix(soapPrefix, soapVersion.getNamespace());
xtw.writeStartElement(soapPrefix,
soapVersion.getEnvelope().getLocalPart(),
soapVersion.getNamespace());
String s2 = xtw.getPrefix(soapVersion.getNamespace());
if (StringUtils.isEmpty(s2) || soapPrefix.equals(s2)) {
xtw.writeNamespace(soapPrefix, soapVersion.getNamespace());
} else {
soapPrefix = s2;
}
}
boolean preexistingHeaders = message.hasHeaders();
if (preexistingHeaders) {
xtw.writeStartElement(soapPrefix,
soapVersion.getHeader().getLocalPart(),
soapVersion.getNamespace());
List<Header> hdrList = message.getHeaders();
for (Header header : hdrList) {
XMLStreamWriter writer = xtw;
if (xtw instanceof W3CDOMStreamWriter) {
Element nd = ((W3CDOMStreamWriter)xtw).getCurrentNode();
if (header.getObject() instanceof Element
&& nd.isSameNode(((Element)header.getObject()).getParentNode())) {
continue;
}
}
if (header instanceof SoapHeader) {
SoapHeader soapHeader = (SoapHeader)header;
writer = new SOAPHeaderWriter(xtw, soapHeader, soapVersion, soapPrefix);
}
DataBinding b = header.getDataBinding();
if (b == null) {
HeaderProcessor hp = bus.getExtension(HeaderManager.class)
.getHeaderProcessor(header.getName().getNamespaceURI());
if (hp != null) {
b = hp.getDataBinding();
}
}
if (b != null) {
MessagePartInfo part = new MessagePartInfo(header.getName(), null);
part.setConcreteName(header.getName());
b.createWriter(XMLStreamWriter.class)
.write(header.getObject(), part, writer);
} else {
Element node = (Element)header.getObject();
StaxUtils.copy(node, writer);
}
}
}
boolean endedHeader = handleHeaderPart(preexistingHeaders, message, soapPrefix);
if (preexistingHeaders && !endedHeader) {
xtw.writeEndElement();
}
xtw.writeStartElement(soapPrefix,
soapVersion.getBody().getLocalPart(),
soapVersion.getNamespace());
// Interceptors followed such as Wrapped/RPC/Doc Interceptor will write SOAP body
} catch (XMLStreamException e) {
throw new SoapFault(
new org.apache.cxf.common.i18n.Message("XML_WRITE_EXC", BUNDLE), e, soapVersion.getSender());
}
}
private boolean handleHeaderPart(boolean preexistingHeaders, SoapMessage message, String soapPrefix) {
//add MessagePart to soapHeader if necessary
boolean endedHeader = false;
Exchange exchange = message.getExchange();
BindingOperationInfo bop = (BindingOperationInfo)exchange.get(BindingOperationInfo.class
.getName());
if (bop == null) {
return endedHeader;
}
XMLStreamWriter xtw = message.getContent(XMLStreamWriter.class);
boolean startedHeader = false;
BindingOperationInfo unwrappedOp = bop;
if (bop.isUnwrapped()) {
unwrappedOp = bop.getWrappedOperation();
}
boolean client = isRequestor(message);
BindingMessageInfo bmi = client ? unwrappedOp.getInput() : unwrappedOp.getOutput();
BindingMessageInfo wrappedBmi = client ? bop.getInput() : bop.getOutput();
if (bmi == null) {
return endedHeader;
}
List<MessagePartInfo> parts = wrappedBmi.getMessageInfo().getMessageParts();
if (parts.size() > 0) {
MessageContentsList objs = MessageContentsList.getContentsList(message);
if (objs == null) {
return endedHeader;
}
SoapVersion soapVersion = message.getVersion();
List<SoapHeaderInfo> headers = bmi.getExtensors(SoapHeaderInfo.class);
if (headers == null) {
return endedHeader;
}
for (SoapHeaderInfo header : headers) {
MessagePartInfo part = header.getPart();
if (wrappedBmi != bmi) {
part = wrappedBmi.getMessageInfo().addMessagePart(part.getName());
}
if (part.getIndex() >= objs.size()) {
// The optional out of band header is not a part of parameters of the method
continue;
}
Object arg = objs.get(part);
if (arg == null) {
continue;
}
objs.remove(part);
if (!(startedHeader || preexistingHeaders)) {
try {
xtw.writeStartElement(soapPrefix,
soapVersion.getHeader().getLocalPart(),
soapVersion.getNamespace());
} catch (XMLStreamException e) {
throw new SoapFault(new org.apache.cxf.common.i18n.Message("XML_WRITE_EXC", BUNDLE),
e, soapVersion.getSender());
}
startedHeader = true;
}
DataWriter<XMLStreamWriter> dataWriter = getDataWriter(message);
dataWriter.write(arg, header.getPart(), xtw);
}
if (startedHeader || preexistingHeaders) {
try {
xtw.writeEndElement();
endedHeader = true;
} catch (XMLStreamException e) {
throw new SoapFault(new org.apache.cxf.common.i18n.Message("XML_WRITE_EXC", BUNDLE),
e, soapVersion.getSender());
}
}
}
return endedHeader;
}
protected DataWriter<XMLStreamWriter> getDataWriter(Message message) {
Service service = ServiceModelUtil.getService(message.getExchange());
DataWriter<XMLStreamWriter> dataWriter = service.getDataBinding().createWriter(XMLStreamWriter.class);
if (dataWriter == null) {
throw new Fault(new org.apache.cxf.common.i18n.Message("NO_DATAWRITER", BUNDLE, service
.getName()));
}
dataWriter.setAttachments(message.getAttachments());
setDataWriterValidation(service, message, dataWriter);
return dataWriter;
}
private void setDataWriterValidation(Service service, Message message, DataWriter<?> writer) {
if (ServiceUtils.isSchemaValidationEnabled(SchemaValidationType.OUT, message)) {
Schema schema = EndpointReferenceUtils.getSchema(service.getServiceInfos().get(0),
message.getExchange().getBus());
writer.setSchema(schema);
}
}
public class SoapOutEndingInterceptor extends AbstractSoapInterceptor {
public SoapOutEndingInterceptor() {
super(SoapOutEndingInterceptor.class.getName(), Phase.WRITE_ENDING);
}
public void handleMessage(SoapMessage message) throws Fault {
try {
XMLStreamWriter xtw = message.getContent(XMLStreamWriter.class);
if (xtw != null) {
// Write body end
xtw.writeEndElement();
// Write Envelope end element
xtw.writeEndElement();
xtw.writeEndDocument();
xtw.flush();
}
} catch (XMLStreamException e) {
if (e.getCause() instanceof EOFException) {
//Nothing we can do about this, some clients will close the connection early if
//they fully parse everything they need
} else {
SoapVersion soapVersion = message.getVersion();
throw new SoapFault(new org.apache.cxf.common.i18n.Message("XML_WRITE_EXC", BUNDLE), e,
soapVersion.getSender());
}
}
}
}
public static class SOAPHeaderWriter extends DelegatingXMLStreamWriter {
final SoapHeader soapHeader;
final SoapVersion soapVersion;
final String soapPrefix;
boolean firstDone;
public SOAPHeaderWriter(XMLStreamWriter writer,
SoapHeader header,
SoapVersion version,
String pfx) {
super(writer);
soapHeader = header;
soapVersion = version;
soapPrefix = pfx;
}
public void writeAttribute(String prefix, String uri, String local, String value)
throws XMLStreamException {
if (soapVersion.getNamespace().equals(uri)
&& (local.equals(soapVersion.getAttrNameMustUnderstand())
|| local.equals(soapVersion.getAttrNameRole()))) {
return;
}
super.writeAttribute(prefix, uri, local, value);
}
public void writeAttribute(String uri, String local, String value) throws XMLStreamException {
if (soapVersion.getNamespace().equals(uri)
&& (local.equals(soapVersion.getAttrNameMustUnderstand())
|| local.equals(soapVersion.getAttrNameRole()))) {
return;
}
super.writeAttribute(uri, local, value);
}
private void writeSoapAttributes() throws XMLStreamException {
if (!firstDone) {
firstDone = true;
if (!StringUtils.isEmpty(soapHeader.getActor())) {
super.writeAttribute(soapPrefix,
soapVersion.getNamespace(),
soapVersion.getAttrNameRole(),
soapHeader.getActor());
}
boolean mu = soapHeader.isMustUnderstand();
if (mu) {
String mul = soapVersion.getAttrValueMustUnderstand(mu);
super.writeAttribute(soapPrefix,
soapVersion.getNamespace(),
soapVersion.getAttrNameMustUnderstand(),
mul);
}
}
}
public void writeStartElement(String arg0, String arg1, String arg2)
throws XMLStreamException {
super.writeStartElement(arg0, arg1, arg2);
writeSoapAttributes();
}
public void writeStartElement(String arg0, String arg1)
throws XMLStreamException {
super.writeStartElement(arg0, arg1);
writeSoapAttributes();
}
public void writeStartElement(String arg0) throws XMLStreamException {
super.writeStartElement(arg0);
writeSoapAttributes();
}
};
}