001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.servicemix.cxfbc.interceptors;
018
019 import java.util.ArrayList;
020 import java.util.List;
021
022 import javax.xml.XMLConstants;
023 import javax.xml.namespace.QName;
024 import javax.xml.stream.XMLStreamException;
025 import javax.xml.stream.XMLStreamReader;
026 import javax.xml.transform.Source;
027 import javax.xml.transform.dom.DOMSource;
028
029 import org.w3c.dom.Document;
030 import org.w3c.dom.Element;
031 import org.w3c.dom.Node;
032 import org.w3c.dom.NodeList;
033
034 import org.apache.cxf.binding.jbi.JBIConstants;
035 import org.apache.cxf.binding.jbi.JBIFault;
036 import org.apache.cxf.binding.soap.Soap11;
037 import org.apache.cxf.binding.soap.SoapHeader;
038 import org.apache.cxf.binding.soap.SoapMessage;
039 import org.apache.cxf.binding.soap.SoapVersion;
040 import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
041 import org.apache.cxf.binding.soap.model.SoapBindingInfo;
042 import org.apache.cxf.binding.soap.model.SoapHeaderInfo;
043 import org.apache.cxf.endpoint.Endpoint;
044 import org.apache.cxf.headers.Header;
045 import org.apache.cxf.helpers.DOMUtils;
046 import org.apache.cxf.interceptor.Fault;
047 import org.apache.cxf.message.Exchange;
048 import org.apache.cxf.message.Message;
049 import org.apache.cxf.phase.Phase;
050 import org.apache.cxf.service.model.BindingMessageInfo;
051 import org.apache.cxf.service.model.BindingOperationInfo;
052 import org.apache.cxf.service.model.MessagePartInfo;
053 import org.apache.cxf.service.model.SchemaInfo;
054 import org.apache.cxf.staxutils.DepthXMLStreamReader;
055 import org.apache.cxf.staxutils.PartialXMLStreamReader;
056 import org.apache.cxf.staxutils.StaxUtils;
057 import org.apache.servicemix.jbi.util.QNameUtil;
058 import org.apache.servicemix.soap.util.DomUtil;
059
060 /**
061 * @author <a href="mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
062 */
063 public class JbiInWsdl1Interceptor extends AbstractSoapInterceptor {
064
065 private boolean useJBIWrapper = true;
066 private boolean useSOAPEnvelope = true;
067
068 public JbiInWsdl1Interceptor(boolean useJBIWrapper, boolean useSOAPEnvelope) {
069 super(Phase.PRE_INVOKE);
070 addAfter(JbiOperationInterceptor.class.getName());
071 this.useJBIWrapper = useJBIWrapper;
072 this.useSOAPEnvelope = useSOAPEnvelope;
073 }
074
075 public void handleMessage(SoapMessage message) {
076 // Ignore faults messages
077 if (message.getContent(Exception.class) != null) {
078 return;
079 }
080 Document document = DomUtil.createDocument();
081
082 if (!useJBIWrapper) {
083
084 SoapVersion soapVersion = message.getVersion();
085 if (useSOAPEnvelope) {
086 Element soapEnv = DomUtil.createElement(document, new QName(
087 soapVersion.getEnvelope().getNamespaceURI(), soapVersion
088 .getEnvelope().getLocalPart(), soapVersion
089 .getPrefix()));
090 Element soapBody = DomUtil.createElement(soapEnv, new QName(
091 soapVersion.getBody().getNamespaceURI(), soapVersion
092 .getBody().getLocalPart(), soapVersion
093 .getPrefix()));
094 soapEnv.appendChild(soapBody);
095 Element body = getBodyElement(message);
096 if (body != null) {
097 soapBody.appendChild(soapBody.getOwnerDocument().importNode(body,
098 true));
099 }
100 } else {
101 Element body = getBodyElement(message);
102 if (body != null) {
103 document.appendChild(document.importNode(body, true));
104 }
105 }
106 } else {
107
108 BindingOperationInfo wsdlOperation = getOperation(message);
109 BindingMessageInfo wsdlMessage = !isRequestor(message) ? wsdlOperation
110 .getInput()
111 : wsdlOperation.getOutput();
112
113 document = DomUtil.createDocument();
114 Element root = DomUtil.createElement(document,
115 JbiConstants.WSDL11_WRAPPER_MESSAGE);
116 String typeNamespace = wsdlMessage.getMessageInfo().getName()
117 .getNamespaceURI();
118 if (typeNamespace == null || typeNamespace.length() == 0) {
119 throw new IllegalArgumentException(
120 "messageType namespace is null or empty");
121 }
122 root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
123 + JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX,
124 typeNamespace);
125
126 root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
127 + JbiConstants.WSDL11_WRAPPER_XSD_PREFIX,
128 XMLConstants.W3C_XML_SCHEMA_NS_URI);
129
130 root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
131 + JbiConstants.WSDL11_WRAPPER_XSI_PREFIX,
132 XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
133
134 String typeLocalName = wsdlMessage.getMessageInfo().getName()
135 .getLocalPart();
136 if (typeLocalName == null || typeLocalName.length() == 0) {
137 throw new IllegalArgumentException(
138 "messageType local name is null or empty");
139 }
140 root.setAttribute(JbiConstants.WSDL11_WRAPPER_TYPE,
141 JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX + ":"
142 + typeLocalName);
143 String messageName = wsdlMessage.getMessageInfo().getName()
144 .getLocalPart();
145 root.setAttribute(JbiConstants.WSDL11_WRAPPER_NAME, messageName);
146 root.setAttribute(JbiConstants.WSDL11_WRAPPER_VERSION, "1.0");
147
148 SoapBindingInfo binding = (SoapBindingInfo) message.getExchange()
149 .get(Endpoint.class).getEndpointInfo().getBinding();
150 String style = binding.getStyle(wsdlOperation.getOperationInfo());
151 if (style == null) {
152 style = binding.getStyle();
153 }
154
155 Element body = getBodyElement(message);
156 if (body == null) {
157 return;
158 }
159
160 if (body.getLocalName().equals("Fault")) {
161 handleJBIFault(message, body);
162 return;
163 }
164 List<SoapHeaderInfo> headers = wsdlMessage
165 .getExtensors(SoapHeaderInfo.class);
166 List<Header> headerElement = message.getHeaders();
167 List<Object> parts = new ArrayList<Object>();
168 for (MessagePartInfo part : wsdlMessage.getMessageParts()) {
169 if ("document".equals(style)) {
170 parts.add(body);
171 } else /* rpc-style */ {
172 // SOAP:Body element is the operation name, children are
173 // operation parameters
174
175 Element param = DomUtil.getFirstChildElement(body);
176 boolean found = false;
177 while (param != null) {
178 if (part.getName().getLocalPart().equals(
179 param.getLocalName())) {
180 found = true;
181 parts.add(wrapNodeList(param.getChildNodes()));
182 break;
183 }
184 param = DomUtil.getNextSiblingElement(param);
185 }
186 if (!found) {
187 throw new Fault(new Exception("Missing part '"
188 + part.getName() + "'"));
189 }
190 }
191 }
192 processHeader(message, headers, headerElement, parts);
193 for (Object part : parts) {
194 if (part instanceof Node) {
195 addPart(root, (Node) part);
196 } else if (part instanceof NodeList) {
197 addPart(root, (NodeList) part);
198 } else if (part instanceof SoapHeader) {
199 addPart(root, (Node) ((SoapHeader) part).getObject());
200 }
201 }
202 }
203 message.setContent(Source.class, new DOMSource(document));
204 }
205
206 private void processHeader(SoapMessage message,
207 List<SoapHeaderInfo> headers, List<Header> headerElement,
208 List<Object> parts) {
209 if (headers != null) {
210 for (SoapHeaderInfo header : headers) {
211 MessagePartInfo part = header.getPart();
212 Header param = findHeader(headerElement, part);
213 int idx = part.getIndex();
214 QName element = part.isElement() ? part.getElementQName() : part.getTypeQName();
215
216
217 Header hdr = getHeaderElement(message, element);
218 if (hdr == null) {
219 throw new Fault(new Exception(
220 "Missing required header element: "
221 + QNameUtil.toString(element)));
222 }
223 if (idx > parts.size()) {
224 parts.add(param);
225 } else if (idx == -1) {
226 parts.add(0, param);
227 } else {
228 parts.add(idx, param);
229 }
230 }
231 }
232 }
233
234 private void handleJBIFault(SoapMessage message, Element soapFault) {
235 Document doc = DomUtil.createDocument();
236 Element jbiFault = DomUtil.createElement(doc, new QName(
237 JBIConstants.NS_JBI_BINDING, JBIFault.JBI_FAULT_ROOT));
238 Node jbiFaultDetail = null;
239 if (message.getVersion() instanceof Soap11) {
240 NodeList nodeList = soapFault.getElementsByTagName("faultcode");
241 String faultCode = nodeList.item(0).getFirstChild().getTextContent();
242 String prefix = faultCode.substring(0, faultCode.indexOf(":"));
243 String localName = faultCode.substring(faultCode.indexOf(":") + 1);
244 message.put("faultcode", new QName(prefix, localName));
245 nodeList = soapFault.getElementsByTagName("faultstring");
246 message.put("faultstring", nodeList.item(0).getFirstChild().getTextContent());
247 nodeList = soapFault.getElementsByTagName("detail");
248 if (nodeList != null && nodeList.getLength() > 0) {
249 jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
250 } else {
251 message.put("hasdetail", false);
252 nodeList = soapFault.getElementsByTagName("faultstring");
253 jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
254 }
255
256 } else {
257 NodeList nodeList = soapFault.getElementsByTagName("soap:Code");
258 String faultCode = nodeList.item(0).getFirstChild().getTextContent();
259 String prefix = faultCode.substring(0, faultCode.indexOf(":"));
260 String localName = faultCode.substring(faultCode.indexOf(":") + 1);
261 message.put("faultcode", new QName(prefix, localName));
262 nodeList = soapFault.getElementsByTagName("soap:Reason");
263 message.put("faultstring", nodeList.item(0).getFirstChild().getTextContent());
264 nodeList = soapFault.getElementsByTagName("soap:Detail");
265 if (nodeList != null && nodeList.getLength() > 0) {
266 jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
267 } else {
268 message.put("hasdetail", false);
269 nodeList = soapFault.getElementsByTagName("faultstring");
270 jbiFaultDetail = doc.importNode(nodeList.item(0).getFirstChild(), true);
271 }
272
273 }
274 SchemaInfo schemaInfo =
275 getOperation(message).getBinding().getService().getSchema(jbiFaultDetail.getNamespaceURI());
276 if (schemaInfo != null && !schemaInfo.isElementFormQualified()) {
277 //that's unquailied fault
278 jbiFaultDetail = addEmptyDefaultTns((Element)jbiFaultDetail);
279
280 }
281 jbiFault.appendChild(jbiFaultDetail);
282 message.setContent(Source.class, new DOMSource(doc));
283 message.put("jbiFault", true);
284 }
285
286 private Element addEmptyDefaultTns(Element ret) {
287
288 if (!ret.hasAttribute("xmlns")) {
289 ret.setAttribute("xmlns", "");
290 }
291 NodeList nodes = ret.getChildNodes();
292 for (int i = 0; i < nodes.getLength(); i++) {
293 if (nodes.item(i) instanceof Element) {
294 Element ele = (Element) nodes.item(i);
295 ele = addEmptyDefaultTns(ele);
296
297 }
298 }
299 return ret;
300 }
301
302
303 private NodeList wrapNodeList(final NodeList childNodes) {
304 return new NodeList() {
305 public int getLength() {
306 return childNodes.getLength();
307 }
308
309 public Node item(int index) {
310 return childNodes.item(index);
311 }
312 };
313 }
314
315 protected BindingOperationInfo getOperation(Message message) {
316 BindingOperationInfo operation = message.getExchange().get(
317 BindingOperationInfo.class);
318 if (operation == null) {
319 throw new Fault(
320 new Exception("Operation not bound on this message"));
321 }
322 return operation;
323 }
324
325 /**
326 * Extract the content as DOM element
327 */
328 protected Element getBodyElement(SoapMessage message) {
329 try {
330 XMLStreamReader xmlReader = message
331 .getContent(XMLStreamReader.class);
332 XMLStreamReader filteredReader = new PartialXMLStreamReader(
333 xmlReader, message.getVersion().getBody());
334 //ensure the whitespace is passed
335 StaxUtils.toNextElement((DepthXMLStreamReader) filteredReader);
336 Document doc = DOMUtils.createDocument();
337 StaxUtils.readDocElements(doc, filteredReader, false);
338 return doc.getDocumentElement();
339 } catch (XMLStreamException e) {
340 throw new Fault(e);
341 }
342 }
343
344 protected Header getHeaderElement(SoapMessage message, QName name) {
345 Exchange exchange = message.getExchange();
346 BindingOperationInfo bop = exchange.get(BindingOperationInfo.class);
347 if (bop.isUnwrapped()) {
348 bop = bop.getWrappedOperation();
349 }
350 boolean client = isRequestor(message);
351 BindingMessageInfo bmi = client ? bop.getOutput() : bop.getInput();
352 if (bmi == null) {
353 // one way operation.
354 return null;
355 }
356 List<SoapHeaderInfo> headers = bmi.getExtensors(SoapHeaderInfo.class);
357 if (headers == null || headers.size() == 0) {
358 return null;
359 }
360 List<Header> headerElement = message.getHeaders();
361 for (SoapHeaderInfo header : headers) {
362 QName qname = header.getPart().isElement()
363 ? header.getPart().getElementQName() : header.getPart().getTypeQName();
364 if (qname.equals(name)) {
365 MessagePartInfo mpi = header.getPart();
366 return findHeader(headerElement, mpi);
367 }
368 }
369 return null;
370 }
371
372 /**
373 * Add a jbi:part to a normalized message document
374 */
375 private static void addPart(Element parent, Node partValue) {
376 Element element = DomUtil.createElement(parent,
377 JbiConstants.WSDL11_WRAPPER_PART);
378 element.appendChild(element.getOwnerDocument().importNode(partValue,
379 true));
380 }
381
382 /**
383 * Add a jbi:part to a normalized message document
384 */
385 private static void addPart(Element parent, NodeList nodes) {
386 Element element = DomUtil.createElement(parent,
387 JbiConstants.WSDL11_WRAPPER_PART);
388 for (int i = 0; i < nodes.getLength(); i++) {
389 Node node = nodes.item(i);
390 element.appendChild(element.getOwnerDocument().importNode(node,
391 true));
392 }
393 }
394
395 private static Header findHeader(List<Header> headerElement,
396 MessagePartInfo mpi) {
397 Header param = null;
398 if (headerElement != null) {
399 QName name = mpi.getConcreteName();
400 for (Header header : headerElement) {
401 if (mpi.isElement()) {
402 if (header.getName().getNamespaceURI() != null
403 && header.getName().getNamespaceURI().equals(
404 name.getNamespaceURI())
405 && header.getName().getLocalPart() != null
406 && header.getName().getLocalPart().equals(
407 name.getLocalPart())) {
408 param = header;
409 }
410 } else {
411 if (header.getName().getLocalPart() != null
412 && header.getName().getLocalPart().equals(
413 name.getLocalPart())) {
414 param = header;
415 }
416 }
417 }
418 }
419 return param;
420 }
421
422 protected boolean isRequestor(Message message) {
423 return Boolean.TRUE.equals(message.get(Message.REQUESTOR_ROLE));
424 }
425 }