Author |
Message |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 12/23/2009 21:00:52
|
stuartw
neo
Joined: 12/23/2009 20:48:56
Messages: 7
Offline
|
I am using Terracotta 3.1.1 for session clustering and hibernate caching. However when I try to use a Spring annotated controller I am getting an exception:
javax.servlet.ServletException: No adapter for handler [au.domain.controller.UserController@1c7e64c]: Does your handler implement a supported interface like Controller?
This only occurs once I run the application with Terracotta, it does not occur if the app runs without Terracotta... hence the post here and not on the Spring forum.
I have read through the documentation for clustering a Spring app but nothing there seems to allude to my problem.
Is there something I need to put into my tc-config.xml file to enable Spring annotated controllers to work?
I can supply any config and code if required.
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 12/24/2009 17:42:19
|
steve
ophanim
Joined: 05/24/2006 14:22:53
Messages: 619
Offline
|
Would be great if you could supply the code.
|
Want to post to this forum? Join the Terracotta Community |
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/04/2010 17:46:25
|
stuartw
neo
Joined: 12/23/2009 20:48:56
Messages: 7
Offline
|
Here is the relevant code
tc-config.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!--
All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
-->
<!-- This is a Terracotta configuration file that has been pre-configured
for use with Tomcat.
For more information, please see the product documentation.
-->
<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-5.xsd">
<!-- Tell DSO where the Terracotta server can be found -->
<servers>
<server host="localhost">
<data>%(user.home)/terracotta/server-data</data>
<logs>%(user.home)/terracotta/server-logs</logs>
</server>
</servers>
<!-- Tell DSO where to put the generated client logs -->
<clients>
<logs>%(user.home)/terracotta/client-logs</logs>
<modules>
<!-- Include the Terracotta Integration Module for the appropriate version of
your sessions container. You can view the list of supported TIMs with the
tim-get tool in the bin directory:
bin/tim-get.sh list
The TIM version will vary depending on the Terracotta release. To get
the correct version, use the tim-get tool in the bin directory to download the
latest appropriate version with a command like:
bin/tim-get.sh install tim-tomcat-6.0
-->
<module name="tim-tomcat-6.0" />
<module name="tim-hibernate-cache-3.3" version="1.0.1" />
<module name="tim-spring-security-2.0" />
</modules>
</clients>
<application>
<dso>
<!-- The following declarations tells DSO which classes should be instrumented to
allow sharing. When the app runs under DSO, shared instances of these classes will
broadcast changes in their state.
A good idiom when writing an app that you intend to cluster via TC DSO is to group the
classes you wish to share under a single package (although if you follow the MVC pattern
this tends to happen naturally) - this way the list of classes you wish to instrument
can be concise -->
<instrumented-classes>
<!-- Start by including just the classes you expect to get added to the shared
graph. These typically include domain classes and shared data structures. If you
miss classes, Terracotta will throw NonPortableOjectExceptions telling you more
about what needs to be added. -->
<include>
<class-expression>au.domain..*</class-expression>
</include>
</instrumented-classes>
<!-- Declare which web application context names should use DSO sessions -->
<web-applications>
<web-application>WebClusterTest</web-application>
</web-applications>
</dso>
</application>
</tc:tc-config>
applicationContext-web.xml
Code:
applicationContext-services.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config />
<bean id="dataSourceTarget"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="com.mysql.jdbc.Driver"
p:jdbcUrl="jdbc:mysql://localhost:3306/test"
p:user="root"
p:password="password" />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"
p:targetDataSource-ref="dataSourceTarget" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource"
p:annotatedClasses="au.domain.model.User">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.provider_class">org.terracotta.hibernate.TerracottaHibernateCacheProvider</prop>
<prop key="hibernate.generate_statistics">true</prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="merge">
<bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener" />
</entry>
</map>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />
<tx:annotation-driven />
<aop:aspectj-autoproxy />
<bean id="performanceMonitorAspect"
class="au.domain.aspects.PerformanceMonitorAspect" />
<bean id="actorService"
class="au.domain.service.ActorServiceImpl" />
<bean id="actorFacade"
class="au.domain.facade.HibernateActorFacade" />
</beans>
UserController.java
Code:
package au.domain.controller;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.support.SessionStatus;
import au.domain.model.User;
import au.domain.service.ActorService;
@Controller()
@RequestMapping("/users.html")
//@SessionAttributes("user")
public class UserController {
ActorService actorService;
/* (non-Javadoc)
* @see au.domain.controller.UserManager#setActorService(au.domain.service.ActorService)
*/
@Autowired
public void setActorService(ActorService actorService) {
this.actorService = actorService;
}
/* (non-Javadoc)
* @see au.domain.controller.UserManager#populateUsers()
*/
@ModelAttribute("users")
public Collection<User> populateUsers() {
return this.actorService.getUsers();
}
/* (non-Javadoc)
* @see au.domain.controller.UserManager#setupForm(org.springframework.ui.ModelMap)
*/
@RequestMapping(method = RequestMethod.GET)
public String setupForm( ModelMap model) {
User user = new User();
model.addAttribute("user", user);
return "users";
}
/* (non-Javadoc)
* @see au.domain.controller.UserManager#processSubmit(au.domain.model.User, org.springframework.validation.BindingResult, org.springframework.web.bind.support.SessionStatus)
*/
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(
@ModelAttribute("user") User user, BindingResult result, SessionStatus status) {
status.setComplete();
this.actorService.createUser(user);
return "redirect:users.html";
}
}
Let me know if you need to look at anything else.
Thanks.
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/04/2010 17:52:28
|
stuartw
neo
Joined: 12/23/2009 20:48:56
Messages: 7
Offline
|
This is the full stack trace:
Code:
05/01/2010 9:49:56 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet Spring MVC Dispatcher Servlet threw exception
javax.servlet.ServletException: No adapter for handler [au.domain.controller.UserController@10b226b]: Does your handler implement a supported interface like Controller?
at org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1100)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:874)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:807)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:501)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:378)
at org.springframework.security.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:109)
at org.springframework.security.intercept.web.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.SessionFixationProtectionFilter.doFilterHttp(SessionFixationProtectionFilter.java:67)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.ExceptionTranslationFilter.doFilterHttp(ExceptionTranslationFilter.java:101)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.providers.anonymous.AnonymousProcessingFilter.doFilterHttp(AnonymousProcessingFilter.java:105)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.rememberme.RememberMeProcessingFilter.doFilterHttp(RememberMeProcessingFilter.java:116)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter.doFilterHttp(SecurityContextHolderAwareRequestFilter.java:91)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.basicauth.BasicProcessingFilter.doFilterHttp(BasicProcessingFilter.java:174)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter.doFilterHttp(DefaultLoginPageGeneratingFilter.java:86)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.AbstractProcessingFilter.doFilterHttp(AbstractProcessingFilter.java:277)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.ui.logout.LogoutFilter.doFilterHttp(LogoutFilter.java:89)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.context.HttpSessionContextIntegrationFilter.doFilterHttp(HttpSessionContextIntegrationFilter.java:235)
at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53)
at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390)
at org.springframework.security.util.FilterChainProxy.doFilter(FilterChainProxy.java:175)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.terracotta.modules.tomcat.tomcat_5_5.SessionValve55.tcInvoke(SessionValve55.java:68)
at org.terracotta.modules.tomcat.tomcat_5_5.SessionValve55.invoke(SessionValve55.java:55)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.jk.server.JkCoyoteHandler.invoke(JkCoyoteHandler.java:190)
at org.apache.jk.common.HandlerRequest.invoke(HandlerRequest.java:291)
at org.apache.jk.common.ChannelSocket.invoke(ChannelSocket.java:769)
at org.apache.jk.common.ChannelSocket.processConnection(ChannelSocket.java:698)
at org.apache.jk.common.ChannelSocket$SocketConnection.runIt(ChannelSocket.java:891)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
at java.lang.Thread.run(Thread.java:619)
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/07/2010 12:13:34
|
teck
seraphim
Joined: 05/24/2006 15:03:25
Messages: 1128
Offline
|
One thing that could help here would be to see this class after it has instrumented by terracotta. It "feels" like somehow the annotation are going missing after we manipulate it.
If you can add -Dtc.classloader.writeToDisk=true to your java startup (presumably in your tomcat start script). That setting should produce a lot of logging on the console saying "Writing resource...". For every type that terracotta instruments a file will be written in ${user.home}/adapated. The version of UserController that ends up there is what I'd like to have a look at.
thanks!
|
Tim Eck (terracotta engineer)
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/07/2010 19:38:02
|
stuartw
neo
Joined: 12/23/2009 20:48:56
Messages: 7
Offline
|
No worries I have attached it here.
Cheers.
Filename |
UserController.class |
Download
|
Description |
UserController adapted class |
Filesize |
4 Kbytes
|
Downloaded: |
304 time(s) |
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/08/2010 15:21:02
|
teck
seraphim
Joined: 05/24/2006 15:03:25
Messages: 1128
Offline
|
Thanks for the instrumented class. Not that I know what spring is looking for exactly but the annotations and interfaces present in the original class are still present in the version after we instrument it.
You've provided the code and config files, but a complete runnable example that reproduces the issue would be even better :-). Is that too big of a request?
|
Tim Eck (terracotta engineer)
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/10/2010 17:43:49
|
stuartw
neo
Joined: 12/23/2009 20:48:56
Messages: 7
Offline
|
No that's fine the application is only a proof of concept. I have attached the war file here.
It should run on a default instance of Tomcat with a default install of MySQL.
I have added these lines to the tomcat startup.bat file:
Code:
set TC_INSTALL_DIR="C:\Program Files\terracotta\terracotta-3.1.1"
set TC_CONFIG_PATH=localhost:9510
call %TC_INSTALL_DIR%\bin\dso-env.bat -q
set JAVA_OPTS=%TC_JAVA_OPTS% -Dtc.classloader.writeToDisk=true
I have it running on two instances(v6.0.20) configured to run load balanced under Apache with mod_jk.
If I remove the above lines the users pages functions normally, otherwise I get the exception first mentioned.
Let me know if I have missed anything, or you need anything else.
Filename |
WebClusterTest.war |
Download
|
Description |
Complete deployable war file. |
Filesize |
12506 Kbytes
|
Downloaded: |
187 time(s) |
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/11/2010 11:02:17
|
teck
seraphim
Joined: 05/24/2006 15:03:25
Messages: 1128
Offline
|
I can run the app, but I'm not entirely sure how to get something in the database so I can login. Bringing up the login page with and without Terracotta seems to work okay.
I made a quick attempt to insert some rows in the DB by hand but that didn't seem to work.
|
Tim Eck (terracotta engineer)
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/11/2010 17:20:40
|
stuartw
neo
Joined: 12/23/2009 20:48:56
Messages: 7
Offline
|
The only credentials that work are:
username: user
password: password
At present the authentication is handled by Spring Security's AuthenticationProvider configured in the file WebContent/WEB-INF/config/applicationContext-web.xml:
Code:
<security:http auto-config="true">
<security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
<security:authentication-provider>
<security:user-service id="userDetailsService">
<security:user name="user" password="password" authorities="ROLE_USER, ROLE_ADMIN" />
</security:user-service>
</security:authentication-provider>
This setup means that only the users listed in that file can authenticate, it does not authenticate against the database stored users.
Sorry, I should have mentioned this before.
Once you have logged in click on the users link(localhost:8080/WebClusterTest/users.html), that is the page that uses the annotated UserController class which seems to fail when Terracotta is used.
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/13/2010 12:40:19
|
teck
seraphim
Joined: 05/24/2006 15:03:25
Messages: 1128
Offline
|
Okee doke, I've managed to get the exception now.
I don't really claim to know what is really going wrong, but the terracotta instrumentation is adding some interfaces to UserController that seem to be interfering with the way spring wants to add advice into that class.
With terracotta it seems to be using a JDK based proxy and without terracotta it is using a CGLIB proxy. I traced things back to the code in spring's DefaultAopProxyFactory.createAopProxy():
http://springframework.cvs.sourceforge.net/viewvc/springframework/spring/src/org/springframework/aop/framework/DefaultAopProxyFactory.java?revision=1.19&view=markup.
The interfaces added by Terracotta make the last condition in there (hasNoUserSuppliedProxyInterfaces(config)) false.
Maybe this is enough information to help you debug this and maybe come up with a workaround?
One thing to note is that you can prevent Terracotta from instrumenting this class pretty easily. I added this to the tc-config.xml (after the include for au.domain..*) and things seemed to work okay:Code:
<exclude>au.domain.controller.UserController</exclude>
Of course exluding this class from instrumentation means that instances of that type cannot be clustered. In general you want to keep the types included for terracotta instrumentation as tight/narrow as possible.
|
Tim Eck (terracotta engineer)
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/13/2010 18:11:43
|
stuartw
neo
Joined: 12/23/2009 20:48:56
Messages: 7
Offline
|
Hi,
Thank you that is great information. There is no reason for the UserController class to be instrumented. It was pure laziness that the entry for instrumented classes in the tc-config.xml was so broad. Excluding that class does indeed resolve the problem.
Having said that however your investigation over which AOP proxy was being used made me look at the PerformanceMonitorAspect class. The around advice for logTime(..) was using the pointcut definition "within(au.domain..*)" (again lazily broad) which forced Spring to use the CGLIB proxies as it needed to proxy classes (such as UserController) as well as interfaces (see http://static.springsource.org/spring/docs/2.5.x/reference/aop.html#aop-introduction-proxies). Limiting the matching join points to interfaces meant that the UserController class can be instrumented by Terracotta and still used by Spring.
Thanks very much for your help!
|
|
 |
![[Post New]](../../templates/default/images/icon_minipost_new.gif) 01/15/2010 13:50:05
|
teck
seraphim
Joined: 05/24/2006 15:03:25
Messages: 1128
Offline
|
good deal. glad I could help
|
Tim Eck (terracotta engineer)
|
|
 |
|