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 }