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.camel.dataformat.soap.name;
018    
019    import java.lang.annotation.Annotation;
020    import java.lang.reflect.Method;
021    import java.util.ArrayList;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    
027    import javax.jws.WebMethod;
028    import javax.jws.WebParam;
029    import javax.jws.WebResult;
030    import javax.xml.namespace.QName;
031    import javax.xml.ws.RequestWrapper;
032    import javax.xml.ws.ResponseWrapper;
033    import javax.xml.ws.WebFault;
034    
035    import org.apache.camel.RuntimeCamelException;
036    
037    import org.slf4j.Logger;
038    import org.slf4j.LoggerFactory;
039    
040    /**
041     * Offers a finder for a webservice interface to determine the QName of a
042     * webservice data element
043     */
044    public class ServiceInterfaceStrategy implements ElementNameStrategy {
045        private static final transient Logger LOG = LoggerFactory.getLogger(ServiceInterfaceStrategy.class);
046        private Map<String, MethodInfo> soapActionToMethodInfo = new HashMap<String, MethodInfo>();
047        private Map<String, QName> inTypeNameToQName = new HashMap<String, QName>();
048        private Map<String, QName> outTypeNameToQName = new HashMap<String, QName>();
049        private boolean isClient;
050        private ElementNameStrategy fallBackStrategy;
051        private Map<QName, Class<? extends Exception>> faultNameToException = new HashMap<QName, Class<? extends Exception>>();
052    
053        /**
054         * Init with JAX-WS service interface
055         * 
056         * @param serviceInterface
057         * @param isClient
058         *            determines if marhalling looks at input or output of method
059         */
060        public ServiceInterfaceStrategy(Class<?> serviceInterface, boolean isClient) {
061            analyzeServiceInterface(serviceInterface);
062            this.isClient = isClient;
063            this.fallBackStrategy = new TypeNameStrategy();
064        }
065        
066        public String getMethodForSoapAction(String soapAction) {
067            MethodInfo methodInfo = soapActionToMethodInfo.get(soapAction);
068            return (methodInfo == null) ? null : methodInfo.getName();
069        }
070    
071        private TypeInfo getOutInfo(Method method) {
072            ResponseWrapper respWrap = method.getAnnotation(ResponseWrapper.class);
073            if (respWrap != null && respWrap.className() != null) {
074                return new TypeInfo(respWrap.className(), 
075                        new QName(respWrap.targetNamespace(), respWrap.localName()));
076            }
077            Class<?> returnType = method.getReturnType();
078            if (Void.TYPE.equals(returnType)) {
079                return new TypeInfo(null, null);
080            } else {
081                Class<?> type = method.getReturnType();
082                WebResult webResult = method.getAnnotation(WebResult.class);
083                if (webResult != null) {
084                    return new TypeInfo(type.getName(), new QName(webResult.targetNamespace(), webResult.name()));
085                } else {
086                    throw new IllegalArgumentException("Result type of method " + method.getName()
087                        + " is not annotated with WebParam. This is not yet supported");
088                }
089            }
090        }
091    
092        private List<TypeInfo> getInInfo(Method method) {
093            List<TypeInfo> typeInfos = new ArrayList<TypeInfo>();
094            RequestWrapper requestWrapper = method.getAnnotation(RequestWrapper.class);
095    
096            // parameter types are returned in declaration order
097            Class<?>[] types = method.getParameterTypes();
098            if (types.length == 0) {
099                typeInfos.add(new TypeInfo(null, null));
100                return typeInfos;
101            }
102            if (requestWrapper != null && requestWrapper.className() != null) {
103                typeInfos.add(new TypeInfo(requestWrapper.className(), 
104                        new QName(requestWrapper.targetNamespace(), requestWrapper.localName())));
105                return typeInfos;
106            }
107                          
108            // annotations are returned in declaration order
109            Annotation[][] annotations = method.getParameterAnnotations();
110    
111            List<WebParam> webParams = new ArrayList<WebParam>();
112    
113            for (int i = 0; i < annotations.length; i++) {
114                Annotation[] singleParameterAnnotations = annotations[i];
115                for (int j = 0; j < singleParameterAnnotations.length; j++) {
116                    Annotation annotation = singleParameterAnnotations[j];
117                    if (annotation instanceof WebParam) {                   
118                        webParams.add((WebParam) annotation);
119                    }
120                }
121            }
122            
123            if (webParams.size() != types.length) {
124                throw new IllegalArgumentException(
125                        "The number of @WebParam annotations for Method " + method.getName()
126                         + " does not match the number of parameters. This is not supported.");
127            }
128    
129            Iterator<WebParam> webParamIter = webParams.iterator();
130            int paramCounter = -1;
131            while (webParamIter.hasNext()) {   
132                WebParam webParam = webParamIter.next();        
133                typeInfos.add(new TypeInfo(types[++paramCounter].getName(),
134                        new QName(webParam.targetNamespace(), webParam.name())));
135            }
136    
137            return typeInfos;
138        }
139        
140    
141        /**
142         * Determines how the parameter object of the service method will be named
143         * in xml. It will use either the RequestWrapper annotation of the method if
144         * present or the WebParam method of the parameter.
145         * 
146         * @param method
147         */
148        private MethodInfo analyzeMethod(Method method) {
149            List<TypeInfo> inInfos = getInInfo(method);
150            TypeInfo outInfo = getOutInfo(method);
151            WebMethod webMethod = method.getAnnotation(WebMethod.class);
152            String soapAction = (webMethod != null) ? webMethod.action() : null;
153            return new MethodInfo(method.getName(), soapAction, 
154                    inInfos.toArray(new TypeInfo[inInfos.size()]), outInfo);
155        }
156    
157        private void analyzeServiceInterface(Class<?> serviceInterface) {
158            Method[] methods = serviceInterface.getMethods();
159            for (Method method : methods) {
160                MethodInfo info = analyzeMethod(method);
161                for (int i = 0; i < info.getIn().length; i++) {
162                    TypeInfo ti = info.getIn()[i];
163                    if (inTypeNameToQName.containsKey(ti.getTypeName())
164                        && (!(ti.getTypeName().equals("javax.xml.ws.Holder")))
165                        && (!(inTypeNameToQName.get(ti.getTypeName()).equals(ti.getElName())))) {
166                        LOG.warn("Ambiguous QName mapping. The type [ "
167                                                        + ti.getTypeName()
168                                                        + " ] is already mapped to a QName in this context.");
169                        continue;
170                    }
171                    inTypeNameToQName.put(ti.getTypeName(), ti.getElName());
172                }
173                if (info.getSoapAction() != null && !"".equals(info.getSoapAction())) {
174                    soapActionToMethodInfo.put(info.getSoapAction(), info);
175                }
176    
177                outTypeNameToQName.put(info.getOut().getTypeName(), info.getOut().getElName());
178    
179                addExceptions(method);
180            }
181        }
182    
183        @SuppressWarnings("unchecked")
184        private void addExceptions(Method method) {
185            Class<?>[] exTypes = method.getExceptionTypes();
186            for (Class<?> exType : exTypes) {
187                WebFault webFault = exType.getAnnotation(WebFault.class);
188                if (webFault != null) {
189                    QName faultName = new QName(webFault.targetNamespace(), webFault.name());
190                    faultNameToException.put(faultName, (Class<? extends Exception>) exType);
191                }
192            }
193        }
194    
195        /**
196         * Determine the QName of the method parameter of the method that matches
197         * either soapAction and type or if not possible only the type
198         * 
199         * @param soapAction
200         * @param type
201         * @return matching QName throws RuntimeException if no matching QName was
202         *         found
203         */
204        public QName findQNameForSoapActionOrType(String soapAction, Class<?> type) {
205            MethodInfo info = soapActionToMethodInfo.get(soapAction);
206            if (info != null) {
207                if (isClient) {
208                    if (type != null) {
209                        return info.getIn(type.getName()).getElName();
210                    } else {
211                        return null;
212                    }
213                } else {
214                    return info.getOut().getElName();
215                }
216            }
217            QName qName = null;
218            if (type != null) {
219                if (isClient) {
220                    qName = inTypeNameToQName.get(type.getName());
221                } else {
222                    qName = outTypeNameToQName.get(type.getName());
223                }
224            }
225            if (qName == null) {
226                try {
227                    qName = fallBackStrategy.findQNameForSoapActionOrType(soapAction, type);
228                } catch (Exception e) {
229                    String msg = "No method found that matches the given SoapAction " + soapAction
230                                 + " or that has an " + (isClient ? "input" : "output") + " of type "
231                                 + type.getName();
232                    throw new RuntimeCamelException(msg, e);
233                }
234            }
235            return qName;
236        }
237    
238        public Class<? extends Exception> findExceptionForFaultName(QName faultName) {
239            return faultNameToException.get(faultName);
240        }
241    
242    }