| /** |
| * 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.camel.dataformat.soap; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.annotation.Annotation; |
| import java.util.ArrayList; |
| import java.util.List; |
| import javax.jws.WebMethod; |
| import javax.jws.WebParam; |
| import javax.xml.bind.JAXBContext; |
| import javax.xml.bind.JAXBElement; |
| import javax.xml.bind.JAXBException; |
| import javax.xml.bind.JAXBIntrospector; |
| import javax.xml.namespace.QName; |
| |
| import org.xml.sax.SAXException; |
| |
| import org.apache.camel.Exchange; |
| import org.apache.camel.Message; |
| import org.apache.camel.RuntimeCamelException; |
| import org.apache.camel.component.bean.BeanInvocation; |
| import org.apache.camel.converter.jaxb.JaxbDataFormat; |
| import org.apache.camel.dataformat.soap.name.ElementNameStrategy; |
| import org.apache.camel.dataformat.soap.name.ServiceInterfaceStrategy; |
| import org.apache.camel.dataformat.soap.name.TypeNameStrategy; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Data format supporting SOAP 1.1 and 1.2. |
| */ |
| public class SoapJaxbDataFormat extends JaxbDataFormat { |
| |
| public static final String SOAP_UNMARSHALLED_HEADER_LIST = "org.apache.camel.dataformat.soap.UNMARSHALLED_HEADER_LIST"; |
| |
| private static final Logger LOG = LoggerFactory.getLogger(SoapJaxbDataFormat.class); |
| |
| private SoapDataFormatAdapter adapter; |
| private ElementNameStrategy elementNameStrategy; |
| private String elementNameStrategyRef; |
| private boolean ignoreUnmarshalledHeaders; |
| private String version; |
| |
| /** |
| * Remember to set the context path when using this constructor |
| */ |
| public SoapJaxbDataFormat() { |
| } |
| |
| /** |
| * Initialize with JAXB context path |
| */ |
| public SoapJaxbDataFormat(String contextPath) { |
| super(contextPath); |
| } |
| |
| /** |
| * Initialize the data format. The serviceInterface is necessary to |
| * determine the element name and namespace of the element inside the soap |
| * body when marshalling |
| */ |
| public SoapJaxbDataFormat(String contextPath, ElementNameStrategy elementNameStrategy) { |
| this(contextPath); |
| this.elementNameStrategy = elementNameStrategy; |
| } |
| |
| /** |
| * Initialize the data format. The serviceInterface is necessary to |
| * determine the element name and namespace of the element inside the soap |
| * body when marshalling |
| */ |
| public SoapJaxbDataFormat(String contextPath, String elementNameStrategyRef) { |
| this(contextPath); |
| this.elementNameStrategyRef = elementNameStrategyRef; |
| } |
| |
| @Override |
| public String getDataFormatName() { |
| return "soapjaxb"; |
| } |
| |
| @Override |
| protected void doStart() throws Exception { |
| if ("1.2".equals(version)) { |
| LOG.debug("Using SOAP 1.2 adapter"); |
| adapter = new Soap12DataFormatAdapter(this); |
| } else { |
| LOG.debug("Using SOAP 1.1 adapter"); |
| adapter = new Soap11DataFormatAdapter(this); |
| } |
| super.doStart(); |
| } |
| |
| protected void checkElementNameStrategy(Exchange exchange) { |
| if (elementNameStrategy == null) { |
| synchronized (this) { |
| if (elementNameStrategy != null) { |
| return; |
| } else { |
| if (elementNameStrategyRef != null) { |
| elementNameStrategy = exchange.getContext().getRegistry().lookupByNameAndType(elementNameStrategyRef, |
| ElementNameStrategy.class); |
| } else { |
| elementNameStrategy = new TypeNameStrategy(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Marshal inputObjects to SOAP xml. If the exchange or message has an |
| * EXCEPTION_CAUGTH property or header then instead of the object the |
| * exception is marshaled. |
| * |
| * To determine the name of the top level xml elements the elementNameStrategy |
| * is used. |
| * @throws IOException,SAXException |
| */ |
| public void marshal(Exchange exchange, Object inputObject, OutputStream stream) throws IOException, SAXException { |
| checkElementNameStrategy(exchange); |
| |
| String soapAction = getSoapActionFromExchange(exchange); |
| if (soapAction == null && inputObject instanceof BeanInvocation) { |
| BeanInvocation beanInvocation = (BeanInvocation) inputObject; |
| WebMethod webMethod = beanInvocation.getMethod().getAnnotation(WebMethod.class); |
| if (webMethod != null && webMethod.action() != null) { |
| soapAction = webMethod.action(); |
| } |
| } |
| |
| Object envelope = adapter.doMarshal(exchange, inputObject, stream, soapAction); |
| |
| // and continue in super |
| super.marshal(exchange, envelope, stream); |
| } |
| |
| /** |
| * Create body content from a non Exception object. If the inputObject is a |
| * BeanInvocation the following should be considered: The first parameter |
| * will be used for the SOAP body. BeanInvocations with more than one |
| * parameter are not supported. So the interface should be in doc lit bare |
| * style. |
| * |
| * @param inputObject |
| * object to be put into the SOAP body |
| * @param soapAction |
| * for name resolution |
| * @param headerElements |
| * in/out parameter used to capture header content if present |
| * |
| * @return JAXBElement for the body content |
| */ |
| protected List<Object> createContentFromObject(final Object inputObject, String soapAction, |
| List<Object> headerElements) { |
| List<Object> bodyParts = new ArrayList<Object>(); |
| List<Object> headerParts = new ArrayList<Object>(); |
| if (inputObject instanceof BeanInvocation) { |
| BeanInvocation bi = (BeanInvocation)inputObject; |
| Annotation[][] annotations = bi.getMethod().getParameterAnnotations(); |
| |
| List<WebParam> webParams = new ArrayList<WebParam>(); |
| for (Annotation[] singleParameterAnnotations : annotations) { |
| for (Annotation annotation : singleParameterAnnotations) { |
| if (annotation instanceof WebParam) { |
| webParams.add((WebParam)annotation); |
| } |
| } |
| } |
| |
| if (webParams.size() > 0) { |
| if (webParams.size() == bi.getArgs().length) { |
| int index = -1; |
| for (Object o : bi.getArgs()) { |
| if (webParams.get(++index).header()) { |
| headerParts.add(o); |
| } else { |
| bodyParts.add(o); |
| } |
| } |
| } else { |
| throw new RuntimeCamelException("The number of bean invocation parameters does not " |
| + "match the number of parameters annotated with @WebParam for the method [ " |
| + bi.getMethod().getName() + "]."); |
| } |
| } else { |
| // try to map all objects for the body |
| for (Object o : bi.getArgs()) { |
| bodyParts.add(o); |
| } |
| } |
| |
| } else { |
| bodyParts.add(inputObject); |
| } |
| |
| List<Object> bodyElements = new ArrayList<Object>(); |
| for (Object bodyObj : bodyParts) { |
| QName name = elementNameStrategy.findQNameForSoapActionOrType(soapAction, bodyObj.getClass()); |
| if (name == null) { |
| LOG.warn("Could not find QName for class " + bodyObj.getClass().getName()); |
| continue; |
| } else { |
| bodyElements.add(getElement(bodyObj, name)); |
| } |
| } |
| |
| for (Object headerObj : headerParts) { |
| QName name = elementNameStrategy.findQNameForSoapActionOrType(soapAction, headerObj.getClass()); |
| if (name == null) { |
| LOG.warn("Could not find QName for class " + headerObj.getClass().getName()); |
| continue; |
| } else { |
| JAXBElement<?> headerElem = getElement(headerObj, name); |
| if (null != headerElem) { |
| headerElements.add(headerElem); |
| } |
| } |
| } |
| |
| return bodyElements; |
| } |
| |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| private JAXBElement<?> getElement(Object fromObj, QName name) { |
| |
| Object value = null; |
| |
| // In the case of a parameter, the class of the value of the holder class |
| // is used for the mapping rather than the holder class itself. |
| |
| if (fromObj instanceof javax.xml.ws.Holder) { |
| javax.xml.ws.Holder holder = (javax.xml.ws.Holder) fromObj; |
| value = holder.value; |
| if (null == value) { |
| return null; |
| } |
| } else { |
| value = fromObj; |
| } |
| |
| return new JAXBElement(name, value.getClass(), value); |
| } |
| |
| /** |
| * Unmarshal a given SOAP xml stream and return the content of the SOAP body |
| * @throws IOException,SAXException |
| */ |
| public Object unmarshal(Exchange exchange, InputStream stream) throws IOException, SAXException { |
| checkElementNameStrategy(exchange); |
| |
| String soapAction = getSoapActionFromExchange(exchange); |
| |
| // Determine the method name for an eventual BeanProcessor in the route |
| if (soapAction != null && elementNameStrategy instanceof ServiceInterfaceStrategy) { |
| ServiceInterfaceStrategy strategy = (ServiceInterfaceStrategy) elementNameStrategy; |
| String methodName = strategy.getMethodForSoapAction(soapAction); |
| exchange.getOut().setHeader(Exchange.BEAN_METHOD_NAME, methodName); |
| } |
| |
| // Store soap action for an eventual later marshal step. |
| // This is necessary as the soap action in the message may get lost on the way |
| if (soapAction != null) { |
| exchange.setProperty(Exchange.SOAP_ACTION, soapAction); |
| } |
| |
| Object unmarshalledObject = super.unmarshal(exchange, stream); |
| Object rootObject = JAXBIntrospector.getValue(unmarshalledObject); |
| |
| return adapter.doUnmarshal(exchange, stream, rootObject); |
| } |
| |
| private String getSoapActionFromExchange(Exchange exchange) { |
| Message inMessage = exchange.getIn(); |
| String soapAction = inMessage .getHeader(Exchange.SOAP_ACTION, String.class); |
| if (soapAction == null) { |
| soapAction = inMessage.getHeader("SOAPAction", String.class); |
| if (soapAction != null && soapAction.startsWith("\"")) { |
| soapAction = soapAction.substring(1, soapAction.length() - 1); |
| } |
| } |
| if (soapAction == null) { |
| soapAction = exchange.getProperty(Exchange.SOAP_ACTION, String.class); |
| } |
| return soapAction; |
| } |
| |
| /** |
| * Added the generated SOAP package to the JAXB context so Soap datatypes |
| * are available |
| */ |
| @Override |
| protected JAXBContext createContext() throws JAXBException { |
| if (getContextPath() != null) { |
| return JAXBContext.newInstance(adapter.getSoapPackageName() + ":" + getContextPath()); |
| } else { |
| return JAXBContext.newInstance(); |
| } |
| } |
| |
| public ElementNameStrategy getElementNameStrategy() { |
| return elementNameStrategy; |
| } |
| |
| public void setElementNameStrategy(Object nameStrategy) { |
| if (nameStrategy == null) { |
| this.elementNameStrategy = null; |
| } else if (nameStrategy instanceof ElementNameStrategy) { |
| this.elementNameStrategy = (ElementNameStrategy) nameStrategy; |
| } else { |
| throw new IllegalArgumentException("The argument for setElementNameStrategy should be subClass of " |
| + ElementNameStrategy.class.getName()); |
| } |
| } |
| |
| public String getElementNameStrategyRef() { |
| return elementNameStrategyRef; |
| } |
| |
| public void setElementNameStrategyRef(String elementNameStrategyRef) { |
| this.elementNameStrategyRef = elementNameStrategyRef; |
| } |
| |
| public boolean isIgnoreUnmarshalledHeaders() { |
| return ignoreUnmarshalledHeaders; |
| } |
| |
| public void setIgnoreUnmarshalledHeaders(boolean ignoreUnmarshalledHeaders) { |
| this.ignoreUnmarshalledHeaders = ignoreUnmarshalledHeaders; |
| } |
| |
| public String getVersion() { |
| return version; |
| } |
| |
| public void setVersion(String version) { |
| this.version = version; |
| } |
| |
| } |
| |