001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.axis2.jaxws.client.proxy;
020:
021: import javax.xml.ws.handler.HandlerResolver;
022: import org.apache.axis2.jaxws.BindingProvider;
023: import org.apache.axis2.jaxws.ExceptionFactory;
024: import org.apache.axis2.jaxws.client.async.AsyncResponse;
025: import org.apache.axis2.jaxws.core.InvocationContext;
026: import org.apache.axis2.jaxws.core.InvocationContextFactory;
027: import org.apache.axis2.jaxws.core.MessageContext;
028: import org.apache.axis2.jaxws.core.controller.AxisInvocationController;
029: import org.apache.axis2.jaxws.core.controller.InvocationController;
030: import org.apache.axis2.jaxws.description.EndpointDescription;
031: import org.apache.axis2.jaxws.description.OperationDescription;
032: import org.apache.axis2.jaxws.description.ServiceDescription;
033: import org.apache.axis2.jaxws.i18n.Messages;
034: import org.apache.axis2.jaxws.marshaller.factory.MethodMarshallerFactory;
035: import org.apache.axis2.jaxws.message.Message;
036: import org.apache.axis2.jaxws.spi.Constants;
037: import org.apache.axis2.jaxws.spi.ServiceDelegate;
038: import org.apache.axis2.jaxws.spi.migrator.ApplicationContextMigratorUtil;
039: import org.apache.commons.logging.Log;
040: import org.apache.commons.logging.LogFactory;
041:
042: import javax.xml.ws.AsyncHandler;
043: import javax.xml.ws.Binding;
044: import javax.xml.ws.Response;
045: import javax.xml.ws.soap.SOAPBinding;
046: import java.lang.reflect.InvocationHandler;
047: import java.lang.reflect.Method;
048: import java.lang.reflect.Modifier;
049: import java.util.concurrent.ExecutorService;
050: import java.util.concurrent.Future;
051:
052: /**
053: * ProxyHandler is the java.lang.reflect.InvocationHandler implementation. When a JAX-WS client
054: * calls the method on a proxy object, created by calling the ServiceDelegate.getPort(...) method,
055: * the inovke method on the ProxyHandler is called.
056: * <p/>
057: * ProxyHandler uses EndpointInterfaceDescriptor and finds out if 1) The client call is Document
058: * Literal or Rpc Literal 2) The WSDL is wrapped or unWrapped.
059: * <p/>
060: * ProxyHandler then reads OperationDescription using Method name called by Client From
061: * OperationDescription it does the following 1) if the wsdl isWrapped() reads RequestWrapper Class
062: * and responseWrapperClass 2) then reads the webParams for the Operation.
063: * <p/>
064: * isWrapped() = true and DocLiteral then ProxyHandler then uses WrapperTool to create Request that
065: * is a Wrapped JAXBObject. Creates JAXBBlock using JAXBBlockFactory Creates MessageContext->Message
066: * and sets JAXBBlock to xmlPart as RequestMsgCtx in InvocationContext. Makes call to
067: * InvocationController. Reads ResponseMsgCtx ->MessageCtx->Message->XMLPart. Converts that to
068: * JAXBlock using JAXBBlockFactory and returns the BO from this JAXBBlock.
069: * <p/>
070: * isWrapped() != true and DocLiteral then ProxyHandler creates the JAXBBlock for the input request
071: * creates a MessageContext that is then used by IbvocationController to invoke. Response is read
072: * and return object is derived using @Webresult annotation. A JAXBBlock is created from the
073: * Response and the BO from JAXBBlock is returned.
074: */
075:
076: public class JAXWSProxyHandler extends BindingProvider implements
077: InvocationHandler {
078: private static Log log = LogFactory.getLog(JAXWSProxyHandler.class);
079:
080: //Reference to ServiceDelegate instance that was used to create the Proxy
081: protected ServiceDescription serviceDesc = null;
082:
083: private Class seiClazz = null;
084:
085: private Method method = null;
086:
087: public JAXWSProxyHandler(ServiceDelegate delegate, Class seiClazz,
088: EndpointDescription epDesc) {
089: super (delegate, epDesc);
090:
091: this .seiClazz = seiClazz;
092: this .serviceDesc = delegate.getServiceDescription();
093: }
094:
095: /* (non-Javadoc)
096: * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
097: *
098: * Invokes the method that was called on the java.lang.reflect.Proxy instance.
099: */
100: public Object invoke(Object proxy, Method method, Object[] args)
101: throws Throwable {
102: boolean debug = log.isDebugEnabled();
103: if (debug) {
104: log
105: .debug("Attemping to invoke Method: "
106: + method.getName());
107: }
108:
109: this .method = method;
110:
111: if (!isValidMethodCall(method)) {
112: throw ExceptionFactory.makeWebServiceException(Messages
113: .getMessage("proxyErr1", method.getName(), seiClazz
114: .getName()));
115: }
116:
117: if (!isPublic(method)) {
118: throw ExceptionFactory
119: .makeWebServiceException(Messages.getMessage(
120: "proxyPrivateMethod", method.getName()));
121: }
122:
123: if (isBindingProviderInvoked(method)) {
124: // Since the JAX-WS proxy instance must also implement the javax.xml.ws.BindingProvider
125: // interface, this object must handle those invocations as well. In that case, we'll
126: // delegate those calls to the BindingProvider object.
127: if (debug) {
128: log
129: .debug("Invoking a public method on the javax.xml.ws.BindingProvider interface.");
130: }
131: try {
132: return method.invoke(this , args);
133: } catch (Throwable e) {
134: if (debug) {
135: log
136: .debug("An error occured while invoking the method: "
137: + e.getMessage());
138: }
139: throw ExceptionFactory.makeWebServiceException(e);
140: }
141: } else {
142: OperationDescription operationDesc = endpointDesc
143: .getEndpointInterfaceDescription().getOperation(
144: method);
145: if (isMethodExcluded(operationDesc)) {
146: throw ExceptionFactory.makeWebServiceException(Messages
147: .getMessage("proxyExcludedMethod", method
148: .getName()));
149: }
150: return invokeSEIMethod(method, args);
151: }
152: }
153:
154: /*
155: * Performs the invocation of the method defined on the Service Endpoint
156: * Interface.
157: */
158: private Object invokeSEIMethod(Method method, Object[] args)
159: throws Throwable {
160: if (log.isDebugEnabled()) {
161: log.debug("Attempting to invoke SEI Method "
162: + method.getName());
163: }
164:
165: OperationDescription operationDesc = endpointDesc
166: .getEndpointInterfaceDescription().getOperation(method);
167:
168: // Create and configure the request MessageContext
169: InvocationContext requestIC = InvocationContextFactory
170: .createInvocationContext(null);
171: MessageContext request = createRequest(method, args);
172: request.setEndpointDescription(getEndpointDescription());
173: request.setOperationDescription(operationDesc);
174:
175: // Enable MTOM on the Message if the property was set on the SOAPBinding.
176: Binding bnd = getBinding();
177: if (bnd != null && bnd instanceof SOAPBinding) {
178: if (((SOAPBinding) bnd).isMTOMEnabled()) {
179: Message requestMsg = request.getMessage();
180: requestMsg.setMTOMEnabled(true);
181: }
182: }
183:
184: /*
185: * TODO: review: make sure the handlers are set on the InvocationContext
186: * This implementation of the JAXWS runtime does not use Endpoint, which
187: * would normally be the place to initialize and store the handler list.
188: * In lieu of that, we will have to intialize and store them on the
189: * InvocationContext. also see the InvocationContextFactory. On the client
190: * side, the binding is not yet set when we call into that factory, so the
191: * handler list doesn't get set on the InvocationContext object there. Thus
192: * we gotta do it here.
193: */
194:
195: // be sure to use whatever handlerresolver is registered on the Service
196: requestIC.setHandlers(bnd.getHandlerChain());
197:
198: requestIC.setRequestMessageContext(request);
199: requestIC.setServiceClient(serviceDelegate
200: .getServiceClient(endpointDesc.getPortQName()));
201:
202: // TODO: Change this to some form of factory so that we can change the IC to
203: // a more simple one for marshaller/unmarshaller testing.
204: InvocationController controller = new AxisInvocationController();
205:
206: // Migrate the properties from the client request context bag to
207: // the request MessageContext.
208: ApplicationContextMigratorUtil
209: .performMigrationToMessageContext(
210: Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
211: getRequestContext(), request);
212:
213: // Check if the call is OneWay, Async or Sync
214: if (operationDesc.isOneWay()) {
215: if (log.isDebugEnabled()) {
216: log.debug("OneWay Call");
217: }
218: controller.invokeOneWay(requestIC);
219:
220: // Check to see if we need to maintain session state
221: checkMaintainSessionState(request, requestIC);
222: }
223:
224: if (method.getReturnType() == Future.class) {
225: if (log.isDebugEnabled()) {
226: log.debug("Async Callback");
227: }
228:
229: //Get AsyncHandler from Objects and sent that to InvokeAsync
230: AsyncHandler asyncHandler = null;
231: for (Object obj : args) {
232: if (obj != null
233: && AsyncHandler.class.isAssignableFrom(obj
234: .getClass())) {
235: asyncHandler = (AsyncHandler) obj;
236: break;
237: }
238: }
239:
240: // Don't allow the invocation to continue if the invocation requires a callback
241: // object, but none was supplied.
242: if (asyncHandler == null) {
243: throw ExceptionFactory.makeWebServiceException(Messages
244: .getMessage("proxyNullCallback"));
245: }
246: AsyncResponse listener = createProxyListener(args,
247: operationDesc);
248: requestIC.setAsyncResponseListener(listener);
249:
250: if ((serviceDelegate.getExecutor() != null)
251: && (serviceDelegate.getExecutor() instanceof ExecutorService)) {
252: ExecutorService es = (ExecutorService) serviceDelegate
253: .getExecutor();
254: if (es.isShutdown()) {
255: // the executor service is shutdown and won't accept new tasks
256: // so return an error back to the client
257: throw ExceptionFactory
258: .makeWebServiceException(Messages
259: .getMessage("ExecutorShutdown"));
260: }
261: }
262:
263: requestIC.setExecutor(serviceDelegate.getExecutor());
264:
265: Future<?> future = controller.invokeAsync(requestIC,
266: asyncHandler);
267:
268: //Check to see if we need to maintain session state
269: checkMaintainSessionState(request, requestIC);
270:
271: return future;
272: }
273:
274: if (method.getReturnType() == Response.class) {
275: if (log.isDebugEnabled()) {
276: log.debug("Async Polling");
277: }
278: AsyncResponse listener = createProxyListener(args,
279: operationDesc);
280: requestIC.setAsyncResponseListener(listener);
281: requestIC.setExecutor(serviceDelegate.getExecutor());
282:
283: Response response = controller.invokeAsync(requestIC);
284:
285: //Check to see if we need to maintain session state
286: checkMaintainSessionState(request, requestIC);
287:
288: return response;
289: }
290:
291: if (!operationDesc.isOneWay()) {
292: InvocationContext responseIC = controller.invoke(requestIC);
293:
294: //Check to see if we need to maintain session state
295: checkMaintainSessionState(request, requestIC);
296:
297: MessageContext responseContext = responseIC
298: .getResponseMessageContext();
299:
300: // Migrate the properties from the response MessageContext back
301: // to the client response context bag.
302: ApplicationContextMigratorUtil
303: .performMigrationFromMessageContext(
304: Constants.APPLICATION_CONTEXT_MIGRATOR_LIST_ID,
305: getResponseContext(), responseContext);
306:
307: Object responseObj = createResponse(method, args,
308: responseContext, operationDesc);
309: return responseObj;
310: }
311:
312: return null;
313: }
314:
315: private AsyncResponse createProxyListener(Object[] args,
316: OperationDescription operationDesc) {
317: ProxyAsyncListener listener = new ProxyAsyncListener(
318: operationDesc);
319: listener.setHandler(this );
320: listener.setInputArgs(args);
321: return listener;
322: }
323:
324: protected boolean isAsync() {
325: String methodName = method.getName();
326: Class returnType = method.getReturnType();
327: return methodName.endsWith("Async")
328: && (returnType.isAssignableFrom(Response.class) || returnType
329: .isAssignableFrom(Future.class));
330: }
331:
332: /**
333: * Creates a request MessageContext for the method call. This request context will be used by
334: * InvocationController to route the method call to axis engine.
335: *
336: * @param method - The method invoked on the proxy object.
337: * @param args - The parameter list
338: * @return A MessageContext that can be used for the invocation
339: */
340: protected MessageContext createRequest(Method method, Object[] args)
341: throws Throwable {
342: if (log.isDebugEnabled()) {
343: log
344: .debug("Creating a new Message using the request parameters.");
345: }
346:
347: OperationDescription operationDesc = endpointDesc
348: .getEndpointInterfaceDescription().getOperation(method);
349:
350: Message message = MethodMarshallerFactory.getMarshaller(
351: operationDesc, true)
352: .marshalRequest(args, operationDesc);
353:
354: if (log.isDebugEnabled()) {
355: log.debug("Request Message created successfully.");
356: }
357:
358: MessageContext request = new MessageContext();
359: request.setMessage(message);
360:
361: // TODO: What happens here might be affected by the property migration plugpoint.
362: request.setProperties(getRequestContext());
363:
364: if (log.isDebugEnabled()) {
365: log.debug("Request MessageContext created successfully.");
366: }
367:
368: return request;
369: }
370:
371: /**
372: * Creates a response MessageContext for the method call. This response context will be used to
373: * create response result to the client call.
374: *
375: * @param method - The method invoked on the proxy object.
376: * @param args - The parameter list.
377: * @param responseContext - The MessageContext to be used for the response.
378: * @param operationDesc - The OperationDescription that for the invoked method.
379: * @return
380: */
381: protected Object createResponse(Method method, Object[] args,
382: MessageContext responseContext,
383: OperationDescription operationDesc) throws Throwable {
384: Message responseMsg = responseContext.getMessage();
385:
386: if (log.isDebugEnabled()) {
387: log
388: .debug("Processing the response Message to create the return value(s).");
389: }
390:
391: // Find out if there was a fault on the response and create the appropriate
392: // exception type.
393: if (hasFaultResponse(responseContext)) {
394: Throwable t = getFaultResponse(responseContext,
395: operationDesc);
396: throw t;
397: }
398:
399: Object object = MethodMarshallerFactory.getMarshaller(
400: operationDesc, false).demarshalResponse(responseMsg,
401: args, operationDesc);
402: if (log.isDebugEnabled()) {
403: log
404: .debug("The response was processed and the return value created successfully.");
405: }
406: return object;
407: }
408:
409: protected static Throwable getFaultResponse(MessageContext msgCtx,
410: OperationDescription opDesc) {
411: Message msg = msgCtx.getMessage();
412: //Operation Description for Async method does not store the fault description as Asyc operation
413: //will never have throws clause in the method signature.
414: //we will fetch the OperationDescription of the sync method and this should give us the
415: //correct fault description so we can throw the right user defined exception.
416:
417: if (opDesc.isJAXWSAsyncClientMethod()) {
418: opDesc = opDesc.getSyncOperation();
419: }
420: if (msg != null && msg.isFault()) {
421: Object object = MethodMarshallerFactory.getMarshaller(
422: opDesc, true).demarshalFaultResponse(msg, opDesc);
423: if (log.isDebugEnabled() && object != null) {
424: log.debug("A fault was found and processed.");
425: log.debug("Throwing a fault of type: "
426: + object.getClass().getName()
427: + " back to the clent.");
428: }
429:
430: return (Throwable) object;
431: } else if (msgCtx.getLocalException() != null) {
432: // use the factory, it'll throw the right thing:
433: return ExceptionFactory.makeWebServiceException(msgCtx
434: .getLocalException());
435: }
436:
437: return null;
438: }
439:
440: protected static boolean hasFaultResponse(MessageContext mc) {
441: if (mc.getMessage() != null && mc.getMessage().isFault())
442: return true;
443: else if (mc.getLocalException() != null)
444: return true;
445: else
446: return false;
447: }
448:
449: private boolean isBindingProviderInvoked(Method method) {
450: Class methodsClass = method.getDeclaringClass();
451: return (seiClazz == methodsClass) ? false : true;
452: }
453:
454: private boolean isValidMethodCall(Method method) {
455: Class clazz = method.getDeclaringClass();
456: if (clazz.isAssignableFrom(seiClazz)
457: || clazz
458: .isAssignableFrom(org.apache.axis2.jaxws.spi.BindingProvider.class)
459: || clazz
460: .isAssignableFrom(javax.xml.ws.BindingProvider.class)) {
461: return true;
462: }
463: return false;
464: }
465:
466: private boolean isPublic(Method method) {
467: return Modifier.isPublic(method.getModifiers());
468: }
469:
470: private boolean isMethodExcluded(OperationDescription operationDesc) {
471: return operationDesc.isExcluded();
472: }
473:
474: public Class getSeiClazz() {
475: return seiClazz;
476: }
477:
478: public void setSeiClazz(Class seiClazz) {
479: this.seiClazz = seiClazz;
480: }
481: }
|