/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed 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.springframework.ws.server.endpoint.adapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.MethodParameter;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.MethodEndpoint;
import org.springframework.ws.server.endpoint.adapter.method.MessageContextMethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.MethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.MethodReturnValueHandler;
import org.springframework.ws.server.endpoint.adapter.method.SourcePayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.StaxPayloadMethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.XPathParamMethodArgumentResolver;
import org.springframework.ws.server.endpoint.adapter.method.dom.Dom4jPayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.dom.DomPayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.dom.JDomPayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.dom.XomPayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.jaxb.JaxbElementPayloadMethodProcessor;
import org.springframework.ws.server.endpoint.adapter.method.jaxb.XmlRootElementPayloadMethodProcessor;
/**
* Default extension of {@link AbstractMethodEndpointAdapter} with support for pluggable {@linkplain
* MethodArgumentResolver argument resolvers} and {@linkplain MethodReturnValueHandler return value handlers}.
*
* @author Arjen Poutsma
* @since 2.0
*/
public class DefaultMethodEndpointAdapter extends AbstractMethodEndpointAdapter
implements BeanClassLoaderAware, InitializingBean {
private static final String DOM4J_CLASS_NAME = "org.dom4j.Element";
private static final String JAXB2_CLASS_NAME = "javax.xml.bind.Binder";
private static final String JDOM_CLASS_NAME = "org.jdom2.Element";
private static final String STAX_CLASS_NAME = "javax.xml.stream.XMLInputFactory";
private static final String XOM_CLASS_NAME = "nu.xom.Element";
private static final String SOAP_METHOD_ARGUMENT_RESOLVER_CLASS_NAME =
"org.springframework.ws.soap.server.endpoint.adapter.method.SoapMethodArgumentResolver";
private static final String SOAP_HEADER_ELEMENT_ARGUMENT_RESOLVER_CLASS_NAME =
"org.springframework.ws.soap.server.endpoint.adapter.method.SoapHeaderElementMethodArgumentResolver";
private List<MethodArgumentResolver> methodArgumentResolvers;
private List<MethodArgumentResolver> customMethodArgumentResolvers;
private List<MethodReturnValueHandler> methodReturnValueHandlers;
private List<MethodReturnValueHandler> customMethodReturnValueHandlers;
private ClassLoader classLoader;
/**
* Returns the list of {@code MethodArgumentResolver}s to use.
*/
public List<MethodArgumentResolver> getMethodArgumentResolvers() {
return methodArgumentResolvers;
}
/**
* Sets the list of {@code MethodArgumentResolver}s to use.
*/
public void setMethodArgumentResolvers(List<MethodArgumentResolver> methodArgumentResolvers) {
this.methodArgumentResolvers = methodArgumentResolvers;
}
/**
* Returns the custom argument resolvers.
*/
public List<MethodArgumentResolver> getCustomMethodArgumentResolvers() {
return customMethodArgumentResolvers;
}
/**
* Sets the custom handlers for method arguments. Custom handlers are
* ordered after built-in ones. To override the built-in support for
* return value handling use {@link #setMethodArgumentResolvers(List)}.
*/
public void setCustomMethodArgumentResolvers(
List<MethodArgumentResolver> customMethodArgumentResolvers) {
this.customMethodArgumentResolvers = customMethodArgumentResolvers;
}
/**
* Returns the list of {@code MethodReturnValueHandler}s to use.
*/
public List<MethodReturnValueHandler> getMethodReturnValueHandlers() {
return methodReturnValueHandlers;
}
/**
* Sets the list of {@code MethodReturnValueHandler}s to use.
*/
public void setMethodReturnValueHandlers(List<MethodReturnValueHandler> methodReturnValueHandlers) {
this.methodReturnValueHandlers = methodReturnValueHandlers;
}
/**
* Returns the custom return value handlers.
*/
public List<MethodReturnValueHandler> getCustomMethodReturnValueHandlers() {
return customMethodReturnValueHandlers;
}
/**
* Sets the handlers for custom return value types. Custom handlers are
* ordered after built-in ones. To override the built-in support for
* return value handling use {@link #setMethodReturnValueHandlers(List)}.
*/
public void setCustomMethodReturnValueHandlers(
List<MethodReturnValueHandler> customMethodReturnValueHandlers) {
this.customMethodReturnValueHandlers = customMethodReturnValueHandlers;
}
private ClassLoader getClassLoader() {
return this.classLoader != null ? this.classLoader : DefaultMethodEndpointAdapter.class.getClassLoader();
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void afterPropertiesSet() throws Exception {
initDefaultStrategies();
}
/** Initialize the default implementations for the adapter's strategies. */
protected void initDefaultStrategies() {
initMethodArgumentResolvers();
initMethodReturnValueHandlers();
}
private void initMethodArgumentResolvers() {
if (CollectionUtils.isEmpty(methodArgumentResolvers)) {
List<MethodArgumentResolver> methodArgumentResolvers = new ArrayList<MethodArgumentResolver>();
methodArgumentResolvers.add(new DomPayloadMethodProcessor());
methodArgumentResolvers.add(new MessageContextMethodArgumentResolver());
methodArgumentResolvers.add(new SourcePayloadMethodProcessor());
methodArgumentResolvers.add(new XPathParamMethodArgumentResolver());
addMethodArgumentResolver(SOAP_METHOD_ARGUMENT_RESOLVER_CLASS_NAME, methodArgumentResolvers);
addMethodArgumentResolver(SOAP_HEADER_ELEMENT_ARGUMENT_RESOLVER_CLASS_NAME, methodArgumentResolvers);
if (isPresent(DOM4J_CLASS_NAME)) {
methodArgumentResolvers.add(new Dom4jPayloadMethodProcessor());
}
if (isPresent(JAXB2_CLASS_NAME)) {
methodArgumentResolvers.add(new XmlRootElementPayloadMethodProcessor());
methodArgumentResolvers.add(new JaxbElementPayloadMethodProcessor());
}
if (isPresent(JDOM_CLASS_NAME)) {
methodArgumentResolvers.add(new JDomPayloadMethodProcessor());
}
if (isPresent(STAX_CLASS_NAME)) {
methodArgumentResolvers.add(new StaxPayloadMethodArgumentResolver());
}
if (isPresent(XOM_CLASS_NAME)) {
methodArgumentResolvers.add(new XomPayloadMethodProcessor());
}
if (logger.isDebugEnabled()) {
logger.debug("No MethodArgumentResolvers set, using defaults: " + methodArgumentResolvers);
}
if (getCustomMethodArgumentResolvers() != null) {
methodArgumentResolvers.addAll(getCustomMethodArgumentResolvers());
}
setMethodArgumentResolvers(methodArgumentResolvers);
}
}
/**
* Certain (SOAP-specific) {@code MethodArgumentResolver}s have to be instantiated by class name, in order to not
* introduce a cyclic dependency.
*/
@SuppressWarnings("unchecked")
private void addMethodArgumentResolver(String className, List<MethodArgumentResolver> methodArgumentResolvers) {
try {
Class<MethodArgumentResolver> methodArgumentResolverClass =
(Class<MethodArgumentResolver>) ClassUtils.forName(className, getClassLoader());
methodArgumentResolvers.add(BeanUtils.instantiate(methodArgumentResolverClass));
}
catch (ClassNotFoundException e) {
logger.warn("Could not find \"" + className + "\" on the classpath");
}
}
private void initMethodReturnValueHandlers() {
if (CollectionUtils.isEmpty(methodReturnValueHandlers)) {
List<MethodReturnValueHandler> methodReturnValueHandlers = new ArrayList<MethodReturnValueHandler>();
methodReturnValueHandlers.add(new DomPayloadMethodProcessor());
methodReturnValueHandlers.add(new SourcePayloadMethodProcessor());
if (isPresent(DOM4J_CLASS_NAME)) {
methodReturnValueHandlers.add(new Dom4jPayloadMethodProcessor());
}
if (isPresent(JAXB2_CLASS_NAME)) {
methodReturnValueHandlers.add(new XmlRootElementPayloadMethodProcessor());
methodReturnValueHandlers.add(new JaxbElementPayloadMethodProcessor());
}
if (isPresent(JDOM_CLASS_NAME)) {
methodReturnValueHandlers.add(new JDomPayloadMethodProcessor());
}
if (isPresent(XOM_CLASS_NAME)) {
methodReturnValueHandlers.add(new XomPayloadMethodProcessor());
}
if (logger.isDebugEnabled()) {
logger.debug("No MethodReturnValueHandlers set, using defaults: " + methodReturnValueHandlers);
}
if (getCustomMethodReturnValueHandlers() != null) {
methodReturnValueHandlers.addAll(getCustomMethodReturnValueHandlers());
}
setMethodReturnValueHandlers(methodReturnValueHandlers);
}
}
private boolean isPresent(String className) {
return ClassUtils.isPresent(className, getClassLoader());
}
@Override
protected boolean supportsInternal(MethodEndpoint methodEndpoint) {
return supportsParameters(methodEndpoint.getMethodParameters()) &&
supportsReturnType(methodEndpoint.getReturnType());
}
private boolean supportsParameters(MethodParameter[] methodParameters) {
for (MethodParameter methodParameter : methodParameters) {
boolean supported = false;
for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
methodParameter.getGenericParameterType() + "]");
}
if (methodArgumentResolver.supportsParameter(methodParameter)) {
supported = true;
break;
}
}
if (!supported) {
return false;
}
}
return true;
}
private boolean supportsReturnType(MethodParameter methodReturnType) {
if (Void.TYPE.equals(methodReturnType.getParameterType())) {
return true;
}
for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
if (methodReturnValueHandler.supportsReturnType(methodReturnType)) {
return true;
}
}
return false;
}
@Override
protected final void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception {
Object[] args = getMethodArguments(messageContext, methodEndpoint);
if (logger.isTraceEnabled()) {
logger.trace("Invoking [" + methodEndpoint + "] with arguments " + Arrays.asList(args));
}
Object returnValue = methodEndpoint.invoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + methodEndpoint + "] returned [" + returnValue + "]");
}
Class<?> returnType = methodEndpoint.getMethod().getReturnType();
if (!Void.TYPE.equals(returnType)) {
handleMethodReturnValue(messageContext, returnValue, methodEndpoint);
}
}
/**
* Returns the argument array for the given method endpoint.
*
* <p>This implementation iterates over the set {@linkplain #setMethodArgumentResolvers(List) argument resolvers} to
* resolve each argument.
*
* @param messageContext the current message context
* @param methodEndpoint the method endpoint to get arguments for
* @return the arguments
* @throws Exception in case of errors
*/
protected Object[] getMethodArguments(MessageContext messageContext, MethodEndpoint methodEndpoint)
throws Exception {
MethodParameter[] parameters = methodEndpoint.getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
for (MethodArgumentResolver methodArgumentResolver : methodArgumentResolvers) {
if (methodArgumentResolver.supportsParameter(parameters[i])) {
args[i] = methodArgumentResolver.resolveArgument(messageContext, parameters[i]);
break;
}
}
}
return args;
}
/**
* Handle the return value for the given method endpoint.
*
* <p>This implementation iterates over the set {@linkplain #setMethodReturnValueHandlers(java.util.List)} return value
* handlers} to resolve the return value.
*
* @param messageContext the current message context
* @param returnValue the return value
* @param methodEndpoint the method endpoint to get arguments for
* @throws Exception in case of errors
*/
protected void handleMethodReturnValue(MessageContext messageContext,
Object returnValue,
MethodEndpoint methodEndpoint) throws Exception {
MethodParameter returnType = methodEndpoint.getReturnType();
for (MethodReturnValueHandler methodReturnValueHandler : methodReturnValueHandlers) {
if (methodReturnValueHandler.supportsReturnType(returnType)) {
methodReturnValueHandler.handleReturnValue(messageContext, returnType, returnValue);
return;
}
}
throw new IllegalStateException(
"Return value [" + returnValue + "] not resolved by any MethodReturnValueHandler");
}
}