Hi,
I have been trying out the spring-jersey integration and ran into an
issue where the
SpringServlet will not work if I configure multiple beans, one for the
root resource class and one for
each subclass of the root class. The SpringServlet.getBeanName() will
throw an exception
since it thinks that there are multiple beans configured for the same
class.
I made some changes to SpringServlet.getBeanName() (see attached file).
In the case where there are multiple bean names returned for a given class,
before throwing an error, I look for the bean instance that has the exact
same class as the one being looked up. With this change, I am able to
make application work.
I would like to submit this change to jersey-spring. Please let me know
if the change
is acceptable or there is a better way to solve this problem.
Thanks.
Peter
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at:
*
https://jersey.dev.java.net/license.txt
* See the License for the specific language governing permissions and
* limitations under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each
* file and include the License file at:
*
https://jersey.dev.java.net/license.txt
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package x.service;
import com.sun.jersey.spi.service.ComponentContext;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.spi.inject.Inject;
import com.sun.jersey.spi.service.ComponentProvider;
import java.lang.reflect.Proxy;
/**
* TODO: DESCRIBE ME<br>
* Created on: Apr 3, 2008<br>
*
* @author <a href="mailto:martin.grotzke_at_freiheit.com">Martin Grotzke</a>
* @version $Id$
*/
public class SpringServlet extends ServletContainer {
private static final long serialVersionUID = 5686655395749077671L;
private static final Log LOG = LogFactory.getLog(SpringServlet.class);
public static class SpringComponentProvider implements ComponentProvider {
/* (non-Javadoc)
* @see com.sun.ws.rest.spi.service.ComponentProvider#getInjectableInstance(java.lang.Object)
*/
public <T> T getInjectableInstance(T instance) {
if (AopUtils.isAopProxy(instance)) {
final Advised aopResource = (Advised) instance;
try {
@SuppressWarnings("unchecked")
final T result = (T) aopResource.getTargetSource().getTarget();
return result;
} catch (Exception e) {
LOG.fatal("Could not get target object from proxy.", e);
throw new RuntimeException("Could not get target object from proxy.", e);
}
} else {
return instance;
}
}
private ConfigurableApplicationContext springContext;
public SpringComponentProvider(ConfigurableApplicationContext springContext) {
this.springContext = springContext;
}
public <T> T getInstance(Scope scope, Class<T> clazz)
throws InstantiationException, IllegalAccessException {
return getInstance(null, scope, clazz);
}
public <T> T getInstance(Scope scope, Constructor<T> constructor,
Object[] parameters)
throws InstantiationException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
return getInstance(null, scope, constructor.getDeclaringClass());
}
public <T> T getInstance(ComponentContext cc, Scope scope, Class<T> clazz)
throws InstantiationException, IllegalAccessException {
final Autowire autowire = clazz.getAnnotation(Autowire.class);
if (autowire != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Creating resource class " + clazz.getSimpleName() + " annotated with @" + Autowire.class.getSimpleName() + " as spring bean.");
}
/* use createBean to have a fully initialized bean, including
* applied BeanPostProcessors (in contrast to #autowire()).
*/
final Object result = springContext.getBeanFactory().createBean(clazz,
autowire.mode().getSpringCode(), autowire.dependencyCheck());
return clazz.cast(result);
}
final String beanName = getBeanName(cc, clazz, springContext);
if (beanName == null) {
return null;
}
/* if the scope is null, this means that jersey simply doesn't know what's
* the scope of this dependency, so it's left to the application...
*/
if (scope == Scope.Undefined || scope == Scope.Singleton && springContext.isSingleton(beanName) || scope == Scope.PerRequest && springContext.isPrototype(beanName)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Retrieving bean '" + beanName + "' for resource class " + clazz.getSimpleName() + " from spring.");
}
Object result = springContext.getBean(beanName);
if (result instanceof Proxy && result instanceof Advised) {
try {
result = ((Advised) result).getTargetSource().getTarget();
} catch (Exception ex) {
return null;
}
}
return clazz.cast(result);
} else {
return null;
}
}
public void inject(Object instance) {
}
};
@Override
protected void initiate(ResourceConfig rc, WebApplication wa) {
try {
final WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
wa.initiate(rc, new SpringComponentProvider((ConfigurableApplicationContext) springContext));
} catch (RuntimeException e) {
LOG.error("Got exception while trying to initialize", e);
throw e;
}
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
LOG.debug("Starting...");
try {
super.service(req, res);
} catch (RuntimeException e) {
LOG.error("Caught exception.", e);
}
LOG.debug("Finished.");
}
private static String getBeanName(ComponentContext cc, Class<?> c, ApplicationContext springContext) {
System.out.println("getBeanName() c = " + c + " cc = " + cc);
boolean annotatedWithInject = false;
if (cc != null) {
final Inject inject = getAnnotation(cc.getAnnotations(), Inject.class);
if (inject != null) {
annotatedWithInject = true;
if (inject.value() != null && !inject.value().equals("")) {
System.out.println("inject.value = " + inject.value());
return inject.value();
}
}
}
final String names[] = springContext.getBeanNamesForType(c);
if (names.length == 0) {
return null;
} else if (names.length == 1) {
return names[0];
} else {
for (int i = 0; i < names.length; i++) {
String name = names[i];
Object bean = springContext.getBean(name);
Class clazz = null;
if (bean instanceof Advised) {
clazz = ((Advised) bean).getTargetSource().getTargetClass();
} else {
clazz = bean.getClass();
}
if (clazz == c) {
return name;
}
}
final StringBuilder sb = new StringBuilder();
sb.append("There are multiple beans configured in spring for the type ").append(c.getName()).append(".");
if (annotatedWithInject) {
sb.append("\nYou should specify the name of the preferred bean at @Inject: Inject(\"yourBean\").");
} else {
sb.append("\nAnnotation information was not available, the reason might be because you're not using " +
"@Inject. You should use @Inject and specifiy the bean name via Inject(\"yourBean\").");
}
sb.append("\nAvailable bean names: ").append(toCSV(names));
throw new RuntimeException(sb.toString());
}
}
private static <T extends Annotation> T getAnnotation(Annotation[] annotations,
Class<T> clazz) {
if (annotations != null) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(clazz)) {
return clazz.cast(annotation);
}
}
}
return null;
}
static <T> String toCSV(T[] items) {
if (items == null) {
return null;
}
return toCSV(Arrays.asList(items));
}
static <I> String toCSV(Collection<I> items) {
return toCSV(items, ", ", null);
}
static <I> String toCSV(Collection<I> items, String separator, String delimiter) {
if (items == null) {
return null;
}
if (items.isEmpty()) {
return "";
}
final StringBuilder sb = new StringBuilder();
for (final Iterator<I> iter = items.iterator(); iter.hasNext();) {
if (delimiter != null) {
sb.append(delimiter);
}
final I item = iter.next();
sb.append(item);
if (delimiter != null) {
sb.append(delimiter);
}
if (iter.hasNext()) {
sb.append(separator);
}
}
return sb.toString();
}
}