/**
* 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.cxf.jaxws;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Provider;
import javax.xml.ws.Response;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.MessageContext.Scope;
import javax.xml.ws.soap.SOAPFaultException;
import org.apache.cxf.annotations.UseAsyncMethod;
import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.continuations.Continuation;
import org.apache.cxf.continuations.ContinuationProvider;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.jaxws.context.WrappedMessageContext;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.FaultMode;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.service.factory.ServiceConstructionException;
import org.apache.cxf.service.invoker.Factory;
import org.apache.cxf.service.invoker.FactoryInvoker;
import org.apache.cxf.service.invoker.SingletonFactory;
import org.apache.cxf.service.model.BindingOperationInfo;
public abstract class AbstractJAXWSMethodInvoker extends FactoryInvoker {
private static final String ASYNC_METHOD = "org.apache.cxf.jaxws.async.method";
private static final String PARTIAL_RESPONSE_SENT_PROPERTY =
"org.apache.cxf.ws.addressing.partial.response.sent";
public AbstractJAXWSMethodInvoker(final Object bean) {
super(new SingletonFactory(bean));
}
public AbstractJAXWSMethodInvoker(Factory factory) {
super(factory);
}
protected SOAPFaultException findSoapFaultException(Throwable ex) {
if (ex instanceof SOAPFaultException) {
return (SOAPFaultException)ex;
}
if (ex.getCause() != null) {
return findSoapFaultException(ex.getCause());
}
return null;
}
@Override
protected Method adjustMethodAndParams(Method mOriginal, Exchange ex, List<Object> params,
Class<?> serviceObjectClass) {
// If class implements Provider<T> interface, use overriden method from service object class
// to check UseAsyncMethod annotation
Method mso = getProviderServiceObjectMethod(mOriginal, serviceObjectClass);
UseAsyncMethod uam = mso.getAnnotation(UseAsyncMethod.class);
if (uam != null) {
BindingOperationInfo bop = ex.getBindingOperationInfo();
Method ret = bop.getProperty(ASYNC_METHOD, Method.class);
if (ret == null) {
Class<?> ptypes[] = new Class<?>[mso.getParameterTypes().length + 1];
System.arraycopy(mso.getParameterTypes(), 0, ptypes, 0, mso.getParameterTypes().length);
ptypes[mso.getParameterTypes().length] = AsyncHandler.class;
try {
ret = mso.getDeclaringClass().getMethod(mso.getName() + "Async", ptypes);
bop.setProperty(ASYNC_METHOD, ret);
} catch (Throwable t) {
//ignore
}
}
if (ret != null) {
JaxwsServerHandler h = ex.get(JaxwsServerHandler.class);
if (h != null) {
return ret;
}
ContinuationProvider cp = ex.getInMessage().get(ContinuationProvider.class);
// Check for decoupled endpoints: if partial response already was sent, ignore continuation
boolean decoupledEndpoints = MessageUtils
.getContextualBoolean(ex.getInMessage(), PARTIAL_RESPONSE_SENT_PROPERTY, false);
if ((cp == null) && uam.always() || decoupledEndpoints) {
JaxwsServerHandler handler = new JaxwsServerHandler(null);
ex.put(JaxwsServerHandler.class, handler);
params.add(handler);
return ret;
} else if (cp != null && cp.getContinuation() != null) {
final Continuation c = cp.getContinuation();
c.suspend(0);
JaxwsServerHandler handler = new JaxwsServerHandler(c);
ex.put(JaxwsServerHandler.class, handler);
params.add(handler);
return ret;
}
}
}
return mOriginal;
}
private Method getProviderServiceObjectMethod(Method m, Class<?> serviceObjectClass) {
if (!Provider.class.isAssignableFrom(serviceObjectClass)) {
return m;
}
Class<?> currentSvcClass = serviceObjectClass;
Class<?> genericType = null;
while (currentSvcClass != null) {
genericType = getProviderGenericType(currentSvcClass);
if (genericType != null) {
break;
}
// Check superclass until top
currentSvcClass = currentSvcClass.getSuperclass();
}
// Should never happens
if (genericType == null) {
return m;
}
try {
return serviceObjectClass.getMethod("invoke", genericType);
} catch (Exception e) {
throw new ServiceConstructionException(e);
}
}
private Class<?> getProviderGenericType(Class<?> svcClass) {
Type[] interfaces = svcClass.getGenericInterfaces();
for (Type interfaceType : interfaces) {
if (interfaceType instanceof ParameterizedType) {
ParameterizedType paramInterface = (ParameterizedType)interfaceType;
if (!paramInterface.getRawType().equals(Provider.class)) {
continue;
}
Type[] typeArgs = paramInterface.getActualTypeArguments();
if (typeArgs.length > 0) {
return (Class<?>)typeArgs[0];
}
}
}
return null;
}
class JaxwsServerHandler implements AsyncHandler<Object> {
Response<Object> r;
Continuation continuation;
boolean done;
JaxwsServerHandler(Continuation c) {
continuation = c;
}
public synchronized void handleResponse(Response<Object> res) {
r = res;
done = true;
if (continuation != null) {
continuation.resume();
}
notifyAll();
}
public boolean hasContinuation() {
return continuation != null;
}
public synchronized void waitForDone() {
while (!done) {
try {
wait();
} catch (InterruptedException e) {
//ignore
}
}
}
public boolean isDone() {
return done;
}
public Object getObject() throws Exception {
return r.get();
}
}
@Override
protected Object invoke(Exchange exchange,
final Object serviceObject, Method m,
List<Object> params) {
JaxwsServerHandler h = exchange.get(JaxwsServerHandler.class);
if (h != null && h.isDone()) {
BindingOperationInfo bop = exchange.getBindingOperationInfo();
if (bop.isUnwrapped()) {
exchange.put(BindingOperationInfo.class, bop.getWrappedOperation());
}
try {
return new MessageContentsList(h.getObject());
} catch (ExecutionException ex) {
exchange.getInMessage().put(FaultMode.class,
FaultMode.CHECKED_APPLICATION_FAULT);
throw createFault(ex.getCause(), m, params, true);
} catch (Exception ex) {
throw createFault(ex.getCause(), m, params, false);
}
}
Object o = super.invoke(exchange, serviceObject, m, params);
if (h != null && !h.hasContinuation()) {
h.waitForDone();
BindingOperationInfo bop = exchange.getBindingOperationInfo();
if (bop.isUnwrapped()) {
exchange.put(BindingOperationInfo.class, bop.getWrappedOperation());
}
try {
return new MessageContentsList(h.getObject());
} catch (ExecutionException ex) {
exchange.getInMessage().put(FaultMode.class,
FaultMode.CHECKED_APPLICATION_FAULT);
throw createFault(ex.getCause(), m, params, true);
} catch (Exception ex) {
throw createFault(ex.getCause(), m, params, false);
}
}
return o;
}
@Override
protected Fault createFault(Throwable ex, Method m, List<Object> params, boolean checked) {
//map the JAX-WS faults
SOAPFaultException sfe = findSoapFaultException(ex);
if (sfe != null) {
SoapFault fault = new SoapFault(sfe.getFault().getFaultString(),
ex,
sfe.getFault().getFaultCodeAsQName());
fault.setRole(sfe.getFault().getFaultActor());
if (sfe.getFault().hasDetail()) {
fault.setDetail(sfe.getFault().getDetail());
}
return fault;
}
return super.createFault(ex, m, params, checked);
}
protected Map<String, Object> removeHandlerProperties(WrappedMessageContext ctx) {
Map<String, Scope> scopes = CastUtils.cast((Map<?, ?>)ctx.get(WrappedMessageContext.SCOPES));
Map<String, Object> handlerScopedStuff = new HashMap<>();
if (scopes != null) {
for (Map.Entry<String, Scope> scope : scopes.entrySet()) {
if (scope.getValue() == Scope.HANDLER) {
handlerScopedStuff.put(scope.getKey(), ctx.get(scope.getKey()));
}
}
for (String key : handlerScopedStuff.keySet()) {
ctx.remove(key);
}
}
return handlerScopedStuff;
}
protected void addHandlerProperties(WrappedMessageContext ctx,
Map<String, Object> handlerScopedStuff) {
for (Map.Entry<String, Object> key : handlerScopedStuff.entrySet()) {
ctx.put(key.getKey(), key.getValue(), Scope.HANDLER);
}
}
private Message createResponseMessage(Exchange exchange) {
if (exchange == null) {
return null;
}
Message m = exchange.getOutMessage();
if (m == null && !exchange.isOneWay()) {
Endpoint ep = exchange.getEndpoint();
m = new MessageImpl();
m.setExchange(exchange);
m = ep.getBinding().createMessage(m);
exchange.setOutMessage(m);
}
return m;
}
protected void updateWebServiceContext(Exchange exchange, MessageContext ctx) {
// Guard against wrong type associated with header list.
// Need to copy header only if the message is going out.
if (ctx.containsKey(Header.HEADER_LIST)
&& ctx.get(Header.HEADER_LIST) instanceof List<?>) {
List<?> list = (List<?>) ctx.get(Header.HEADER_LIST);
if (list != null && !list.isEmpty()) {
SoapMessage sm = (SoapMessage) createResponseMessage(exchange);
if (sm != null) {
Iterator<?> iter = list.iterator();
while (iter.hasNext()) {
sm.getHeaders().add((Header) iter.next());
}
}
}
}
if (exchange.getOutMessage() != null) {
Message out = exchange.getOutMessage();
if (out.containsKey(Message.PROTOCOL_HEADERS)) {
Map<String, List<String>> heads = CastUtils
.cast((Map<?, ?>)exchange.getOutMessage().get(Message.PROTOCOL_HEADERS));
if (heads.containsKey("Content-Type")) {
List<String> ct = heads.get("Content-Type");
exchange.getOutMessage().put(Message.CONTENT_TYPE, ct.get(0));
heads.remove("Content-Type");
}
}
}
}
protected void updateHeader(Exchange exchange, MessageContext ctx) {
if (ctx.containsKey(Header.HEADER_LIST)
&& ctx.get(Header.HEADER_LIST) instanceof List<?>) {
List<?> list = (List<?>) ctx.get(Header.HEADER_LIST);
if (list != null && !list.isEmpty()) {
SoapMessage sm = (SoapMessage) createResponseMessage(exchange);
if (sm != null) {
Iterator<?> iter = list.iterator();
while (iter.hasNext()) {
Header header = (Header) iter.next();
if (header.getDirection() != Header.Direction.DIRECTION_IN
&& !header.getName().getNamespaceURI().
equals("http://docs.oasis-open.org/wss/2004/01/"
+ "oasis-200401-wss-wssecurity-secext-1.0.xsd")
&& !header.getName().getNamespaceURI().
equals("http://docs.oasis-open.org/"
+ "wss/oasis-wss-wssecurity-secext-1.1.xsd")) {
//don't copy over security header, out interceptor chain will take care of it.
sm.getHeaders().add(header);
}
}
}
}
}
}
}