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.soap.util.DomUtil;
058
059 /**
060 * @author <a href="mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
061 */
062 public class JbiInWsdl1Interceptor extends AbstractSoapInterceptor {
063
064 private boolean useJBIWrapper = true;
065 private boolean useSOAPEnvelope = true;
066
067 public JbiInWsdl1Interceptor(boolean useJBIWrapper, boolean useSOAPEnvelope) {
068 super(Phase.PRE_INVOKE);
069 addAfter(JbiOperationInterceptor.class.getName());
070 this.useJBIWrapper = useJBIWrapper;
071 this.useSOAPEnvelope = useSOAPEnvelope;
072 }
073
074 public void handleMessage(SoapMessage message) {
075 // Ignore faults messages
076 if (message.getContent(Exception.class) != null) {
077 return;
078 }
079 Document document = DomUtil.createDocument();
080
081 if (!useJBIWrapper) {
082
083 SoapVersion soapVersion = message.getVersion();
084 if (useSOAPEnvelope) {
085 Element soapEnv = DomUtil.createElement(document, new QName(
086 soapVersion.getEnvelope().getNamespaceURI(), soapVersion
087 .getEnvelope().getLocalPart(), soapVersion
088 .getPrefix()));
089 Element soapBody = DomUtil.createElement(soapEnv, new QName(
090 soapVersion.getBody().getNamespaceURI(), soapVersion
091 .getBody().getLocalPart(), soapVersion
092 .getPrefix()));
093 soapEnv.appendChild(soapBody);
094 Element body = getBodyElement(message);
095 if (body != null) {
096 soapBody.appendChild(soapBody.getOwnerDocument().importNode(body,
097 true));
098 }
099 } else {
100 Element body = getBodyElement(message);
101 if (body != null) {
102 document.appendChild(document.importNode(body, true));
103 }
104 }
105 } else {
106
107 BindingOperationInfo wsdlOperation = getOperation(message);
108 BindingMessageInfo wsdlMessage = !isRequestor(message) ? wsdlOperation
109 .getInput()
110 : wsdlOperation.getOutput();
111
112 document = DomUtil.createDocument();
113 Element root = DomUtil.createElement(document,
114 JbiConstants.WSDL11_WRAPPER_MESSAGE);
115 String typeNamespace = wsdlMessage.getMessageInfo().getName()
116 .getNamespaceURI();
117 if (typeNamespace == null || typeNamespace.length() == 0) {
118 throw new IllegalArgumentException(
119 "messageType namespace is null or empty");
120 }
121 root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
122 + JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX,
123 typeNamespace);
124
125 root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
126 + JbiConstants.WSDL11_WRAPPER_XSD_PREFIX,
127 XMLConstants.W3C_XML_SCHEMA_NS_URI);
128
129 root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":"
130 + JbiConstants.WSDL11_WRAPPER_XSI_PREFIX,
131 XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
132
133 String typeLocalName = wsdlMessage.getMessageInfo().getName()
134 .getLocalPart();
135 if (typeLocalName == null || typeLocalName.length() == 0) {
136 throw new IllegalArgumentException(
137 "messageType local name is null or empty");
138 }
139 root.setAttribute(JbiConstants.WSDL11_WRAPPER_TYPE,
140 JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX + ":"
141 + typeLocalName);
142 String messageName = wsdlMessage.getMessageInfo().getName()
143 .getLocalPart();
144 root.setAttribute(JbiConstants.WSDL11_WRAPPER_NAME, messageName);
145 root.setAttribute(JbiConstants.WSDL11_WRAPPER_VERSION, "1.0");
146
147 SoapBindingInfo binding = (SoapBindingInfo) message.getExchange()
148 .get(Endpoint.class).getEndpointInfo().getBinding();
149 String style = binding.getStyle(wsdlOperation.getOperationInfo());
150 if (style == null) {
151 style = binding.getStyle();
152 }
153
154 Element body = getBodyElement(message);
155 if (body == null) {
156 return;
157 }
158
159 if (body.getLocalName().equals("Fault")) {
160 handleJBIFault(message, body);
161 return;
162 }
163 List<SoapHeaderInfo> headers = wsdlMessage
164 .getExtensors(SoapHeaderInfo.class);
165 List<Header> headerElement = message.getHeaders();
166 List<Object> parts = new ArrayList<Object>();
167 for (MessagePartInfo part : wsdlMessage.getMessageParts()) {
168 if ("document".equals(style)) {
169 parts.add(body);
170 } else /* rpc-style */ {
171 // SOAP:Body element is the operation name, children are
172 // operation parameters
173
174 Element param = DomUtil.getFirstChildElement(body);
175 boolean found = false;
176 while (param != null) {
177 if (part.getName().getLocalPart().equals(
178 param.getLocalName())) {
179 found = true;
180 parts.add(wrapNodeList(param.getChildNodes()));
181 break;
182 }
183 param = DomUtil.getNextSiblingElement(param);
184 }
185 if (!found) {
186 throw new Fault(new Exception("Missing part '"
187 + part.getName() + "'"));
188 }
189 }
190 }
191 processHeader(message, headers, headerElement, parts);
192 for (Object part : parts) {
193 if (part instanceof Node) {
194 addPart(root, (Node) part);
195 } else if (part instanceof NodeList) {
196 addPart(root, (NodeList) part);
197 } else if (part instanceof SoapHeader) {
198 addPart(root, (Node) ((SoapHeader) part).getObject());
199 }
200 }
201 }
202 message.setContent(Source.class, new DOMSource(document));
203 }
204
205 private void processHeader(SoapMessage message,
206 List<SoapHeaderInfo> headers, List<Header> headerElement,
207 List<Object> parts) {
208 if (headers != null) {
209 for (SoapHeaderInfo header : headers) {
210 MessagePartInfo part = header.getPart();
211 Header param = findHeader(headerElement, part);
212 int idx = part.getIndex();
213
214 if (idx > parts.size()) {
215 parts.add(param);
216 } else if (idx == -1) {
217 parts.add(0, param);
218 } else {
219 parts.add(idx, param);
220 }
221 }
222 }
223 }
224
225 void handleJBIFault(SoapMessage message, Element soapFault) {
226 try {
227 Document doc = DomUtil.createDocument();
228 Element jbiFault = DomUtil.createElement(doc, new QName(
229 JBIConstants.NS_JBI_BINDING, JBIFault.JBI_FAULT_ROOT));
230 Node jbiFaultDetail = null;
231 if (message.getVersion() instanceof Soap11) {
232 NodeList nodeList = soapFault.getElementsByTagName("faultcode");
233 String faultCode = nodeList.item(0).getFirstChild()
234 .getTextContent();
235 String prefix = faultCode.substring(0, faultCode.indexOf(":"));
236 String localName = faultCode
237 .substring(faultCode.indexOf(":") + 1);
238 message.put("faultcode", new QName(prefix, localName));
239 nodeList = soapFault.getElementsByTagName("faultstring");
240 message.put("faultstring", nodeList.item(0).getFirstChild()
241 .getTextContent());
242 nodeList = soapFault.getElementsByTagName("detail");
243 if (nodeList != null
244 && nodeList.getLength() > 0
245 && DomUtil.getFirstChildElement(nodeList.item(0)) != null) {
246 jbiFaultDetail = doc.importNode(DomUtil
247 .getFirstChildElement(nodeList.item(0)), true);
248 } else {
249 message.put("hasdetail", false);
250 nodeList = soapFault.getElementsByTagName("faultstring");
251 jbiFaultDetail = doc.importNode(nodeList.item(0)
252 .getFirstChild(), true);
253 }
254
255 } else {
256 NodeList nodeList = soapFault.getElementsByTagName("soap:Code");
257 String faultCode = DomUtil.getFirstChildElement(
258 nodeList.item(0)).getTextContent();
259 String prefix = faultCode.substring(0, faultCode.indexOf(":"));
260 String localName = faultCode
261 .substring(faultCode.indexOf(":") + 1);
262 message.put("faultcode", new QName(prefix, localName));
263 nodeList = soapFault.getElementsByTagName("soap:Reason");
264 message.put("faultstring", DomUtil.getFirstChildElement(
265 nodeList.item(0)).getTextContent());
266 nodeList = soapFault.getElementsByTagName("soap:Detail");
267 if (nodeList != null
268 && nodeList.getLength() > 0
269 && DomUtil.getFirstChildElement(nodeList.item(0)) != null) {
270 jbiFaultDetail = doc.importNode(DomUtil
271 .getFirstChildElement(nodeList.item(0)), true);
272 } else {
273 message.put("hasdetail", false);
274 nodeList = soapFault.getElementsByTagName("faultstring");
275 jbiFaultDetail = doc.importNode(DomUtil
276 .getFirstChildElement(nodeList.item(0)), true);
277 }
278
279 }
280 SchemaInfo schemaInfo = getOperation(message).getBinding()
281 .getService().getSchema(jbiFaultDetail.getNamespaceURI());
282 if (schemaInfo != null && !schemaInfo.isElementFormQualified()) {
283 // that's unquailied fault
284 jbiFaultDetail = addEmptyDefaultTns((Element) jbiFaultDetail);
285
286 }
287 jbiFault.appendChild(jbiFaultDetail);
288 message.setContent(Source.class, new DOMSource(doc));
289 message.put("jbiFault", true);
290 } catch (Exception e) {
291 throw new RuntimeException("the fault message can't be parsed:"
292 + e.getMessage());
293
294 }
295 }
296
297 private Element addEmptyDefaultTns(Element ret) {
298
299 if (!ret.hasAttribute("xmlns")) {
300 ret.setAttribute("xmlns", "");
301 }
302 NodeList nodes = ret.getChildNodes();
303 for (int i = 0; i < nodes.getLength(); i++) {
304 if (nodes.item(i) instanceof Element) {
305 Element ele = (Element) nodes.item(i);
306 ele = addEmptyDefaultTns(ele);
307
308 }
309 }
310 return ret;
311 }
312
313
314 private NodeList wrapNodeList(final NodeList childNodes) {
315 return new NodeList() {
316 public int getLength() {
317 return childNodes.getLength();
318 }
319
320 public Node item(int index) {
321 return childNodes.item(index);
322 }
323 };
324 }
325
326 protected BindingOperationInfo getOperation(Message message) {
327 BindingOperationInfo operation = message.getExchange().get(
328 BindingOperationInfo.class);
329 if (operation == null) {
330 throw new Fault(
331 new Exception("Operation not bound on this message"));
332 }
333 return operation;
334 }
335
336 /**
337 * Extract the content as DOM element
338 */
339 protected Element getBodyElement(SoapMessage message) {
340 try {
341 XMLStreamReader xmlReader = message
342 .getContent(XMLStreamReader.class);
343 XMLStreamReader filteredReader = new PartialXMLStreamReader(
344 xmlReader, message.getVersion().getBody());
345 //ensure the whitespace is passed
346 StaxUtils.toNextElement((DepthXMLStreamReader) filteredReader);
347 Document doc = DOMUtils.createDocument();
348 StaxUtils.readDocElements(doc, filteredReader, true);
349 return doc.getDocumentElement();
350 } catch (XMLStreamException e) {
351 throw new Fault(e);
352 }
353 }
354
355 protected Header getHeaderElement(SoapMessage message, QName name) {
356 Exchange exchange = message.getExchange();
357 BindingOperationInfo bop = exchange.get(BindingOperationInfo.class);
358 if (bop.isUnwrapped()) {
359 bop = bop.getWrappedOperation();
360 }
361 boolean client = isRequestor(message);
362 BindingMessageInfo bmi = client ? bop.getOutput() : bop.getInput();
363 if (bmi == null) {
364 // one way operation.
365 return null;
366 }
367 List<SoapHeaderInfo> headers = bmi.getExtensors(SoapHeaderInfo.class);
368 if (headers == null || headers.size() == 0) {
369 return null;
370 }
371 List<Header> headerElement = message.getHeaders();
372 for (SoapHeaderInfo header : headers) {
373 QName qname = header.getPart().isElement()
374 ? header.getPart().getElementQName() : header.getPart().getTypeQName();
375 if (qname.equals(name)) {
376 MessagePartInfo mpi = header.getPart();
377 return findHeader(headerElement, mpi);
378 }
379 }
380 return null;
381 }
382
383 /**
384 * Add a jbi:part to a normalized message document
385 */
386 private static void addPart(Element parent, Node partValue) {
387 Element element = DomUtil.createElement(parent,
388 JbiConstants.WSDL11_WRAPPER_PART);
389 element.appendChild(element.getOwnerDocument().importNode(partValue,
390 true));
391 }
392
393 /**
394 * Add a jbi:part to a normalized message document
395 */
396 private static void addPart(Element parent, NodeList nodes) {
397 Element element = DomUtil.createElement(parent,
398 JbiConstants.WSDL11_WRAPPER_PART);
399 for (int i = 0; i < nodes.getLength(); i++) {
400 Node node = nodes.item(i);
401 element.appendChild(element.getOwnerDocument().importNode(node,
402 true));
403 }
404 }
405
406 private static Header findHeader(List<Header> headerElement,
407 MessagePartInfo mpi) {
408 Header param = null;
409 if (headerElement != null) {
410 QName name = mpi.getConcreteName();
411 for (Header header : headerElement) {
412 if (mpi.isElement()) {
413 if (header.getName().getNamespaceURI() != null
414 && header.getName().getNamespaceURI().equals(
415 name.getNamespaceURI())
416 && header.getName().getLocalPart() != null
417 && header.getName().getLocalPart().equals(
418 name.getLocalPart())) {
419 param = header;
420 }
421 } else {
422 if (header.getName().getLocalPart() != null
423 && header.getName().getLocalPart().equals(
424 name.getLocalPart())) {
425 param = header;
426 }
427 }
428 }
429 }
430 return param;
431 }
432
433 protected boolean isRequestor(Message message) {
434 return Boolean.TRUE.equals(message.get(Message.REQUESTOR_ROLE));
435 }
436 }