Java Servlet API can forward requests to another path within the same server (identical host:port). But, forwarding to a different host:port — like proxy do — is another story.
I've tried to do that with Jersey Client, adapting the ServletRequest — method, headers, mediatype and body — to a Jersey ClientRequest (with a different base uri), making the call, and adapting back the Jersey ClientResponse — method, headers, mediatype and body — to the ServletResponse.
Adapting those manually seems wrong to me.
Isn't there a pure Servlet API solution?
Or an HTTP client capable of adapting requests back and forth when changing the host:port?
HTTP-Proxy-Servlet does exactly what you need.
Quick configuration
pom.xml
<dependency>
<groupId>org.mitre.dsmiley.httpproxy</groupId>
<artifactId>smiley-http-proxy-servlet</artifactId>
<version>1.7</version>
</dependency>
web.xml
<servlet>
<servlet-name>solr</servlet-name>
<servlet-class>org.mitre.dsmiley.httpproxy.ProxyServlet</servlet-class>
<init-param>
<param-name>targetUri</param-name>
<param-value>http://solrserver:8983/solr</param-value>
</init-param>
<init-param>
<param-name>log</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>solr</servlet-name>
<url-pattern>/solr/*</url-pattern>
</servlet-mapping>
Spring Integration
see also: HTTP-Proxy-Servlet Issue #15
pom.xml
<dependency>
<groupId>org.mitre.dsmiley.httpproxy</groupId>
<artifactId>smiley-http-proxy-servlet</artifactId>
<version>1.7</version>
</dependency>
ServletWrappingControllerExt.java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ServletWrappingController;
public class ServletWrappingControllerExt extends ServletWrappingController
{
private String pathToStrip;
public void setPathToStrip(String pathToStrip)
{
this.pathToStrip = pathToStrip;
}
#Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception
{
final HttpServletRequest wrapper = new HttpServletRequestWrapper(request)
{
#Override
public String getPathInfo()
{
//Please note that getPathInfo returns null if DispatcherServlet is configured to track url-pattern "/"
//It should be configured to track url-pattern "/*". Below is a sample DispatcherServlet configuration
/*
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
*/
String path = super.getPathInfo();
if (path.startsWith(pathToStrip))
{
final int length = pathToStrip.length();
path = path.substring(length);
}
return path;
}
#Override
public String getServletPath()
{
return super.getServletPath();
}
};
return super.handleRequestInternal(wrapper, response);
}
}
Beans configuration
<bean id="myServletWrapper" class="ServletWrappingControllerExt">
<property name="pathToStrip" value="/solr"/>
<property name="servletClass" value="org.mitre.dsmiley.httpproxy.ProxyServlet" />
<property name="servletName" value="solr" />
<property name="initParameters">
<props>
<prop key="targetUri">http://solrserver:8983/solr</prop>
<prop key="log">true</prop>
</props>
</property>
</bean>
<bean id="myServletUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/solr/**" value-ref="myServletWrapper" />
</map>
</property>
<property name="order" value="1" />
</bean>
You should use javax.net.HttpURLConnection
Here is the psuedo code for that:
URL url = new URL("http://otherserver:otherport/url");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
// set http method if required
connection.setRequestMethod("POST");
// set request header if required
connection.setRequestProperty("header1", "value1");
// check status code
if(connection.getResponseCode() == 200) {
InputStream is = connection.getInputStream();
//transfer is to the required output stream
} else {
//write error
}
As far as I understand You need to send requests from Servlet and get response from other server into yours, may be you need a HTTP Client (Overview) for that.
This question might also help you.
Related
I'm trying to modify a wicket application to store the session in redis via spring-session. The session is showing up in redis, but I've run into a problem that whenever the application makes a standard wicket ajax call, the response from wicket includes an Ajax-Location header that is interpreted by wicket-ajax-jquery.js triggering a page redirect. But this only happens AFTER the first ajax call has been successful. For example, the first ajax call may look like this:
http://host:port/context/help/admin?0-1.IBehaviorListener.0-smartTable-tableArea-records-0-row-detailToggleCell-detailToggleLink&_=1636756805561
and the response headers do NOT include Ajax-Location. And then later, the next ajax call may look like this:
http://host:port/context/help/admin?1-1.IBehaviorListener.0-smartTable-tableArea-records-0-row-detailToggleCell-detailToggleLink&_=1636756906417
But the response header now includes this:
Ajax-Location: ./admin?2
and instead of just doing the ajax update to the page, the entire page redirects to the URL specified in that header because of code in src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js
Digging down into the wicket-core code using the debugger, consider this where it doesn't produce the Ajax-Location header and works properly:
Step completed: "thread=ba6f07:3", org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.respond(), line=197 bci=169
ba6f07:3[1] print canCallListenerInterfaceAfterExpiry
canCallListenerInterfaceAfterExpiry = false
ba6f07:3[1] print freshPage
freshPage = false
ba6f07:3[1] print isStateless
isStateless = false
ba6f07:3[1] print component
component = "[AjaxLink [Component id = detailToggleLink]]"
and then compare with this where it DOES produce an Ajax-Location header and doesn't work properly:
Breakpoint hit: "thread=ba6f07:7", org.apache.wicket.core.request.handler.ListenerInterfaceRequestHandler.respond(), line=197 bci=169
ba6f07:7[1] print canCallListenerInterfaceAfterExpiry
canCallListenerInterfaceAfterExpiry = false
ba6f07:7[1] print freshPage
freshPage = true
ba6f07:7[1] print isStateless
isStateless = false
ba6f07:7[1] print component
component = null
The difference being that when it doesn't work, freshPage is true and component is null.
Note: this pattern is fully functional in another similar application that I have and I’ve spent some time comparing the two. Clearly, something is missing from the original application in the app that I’m working on but I haven’t been able to identify it yet.
My redis http session config class looks like this:
import javax.annotation.PostConstruct;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
import org.springframework.session.web.http.CookieHttpSessionStrategy;
import org.springframework.session.web.http.DefaultCookieSerializer;
#Configuration
#EnableRedisHttpSession
public class MyRedisHttpSessionConfig extends RedisHttpSessionConfiguration
{
private JedisConnectionFactory connectionFactory;
#PostConstruct
public void init()
{
CookieHttpSessionStrategy strategy = new CookieHttpSessionStrategy();
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName( "SESSION" );
strategy.setCookieSerializer(cookieSerializer);
setHttpSessionStrategy( strategy );
}
#Bean
public JedisConnectionFactory connectionFactory() throws Exception
{
return connectionFactory;
}
public void setConnectionFactory( JedisConnectionFactory connectionFactory )
{
this.connectionFactory = connectionFactory;
}
}
my web.xml has this:
...
<filter>
<filter-name>requestLoggingFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>myApplicationWicketFilter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
<init-param>
<param-name>filterMappingUrlPattern</param-name>
<param-value>/*</param-value>
</init-param>
</filter>
...
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
<filter-mapping>
<filter-name>ariesApplicationWicketFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
...
and my spring beans config file has this:
...
<!-- The RedisHttpSessionConfiguration creates an http Filter bean with name "springSessionRepositoryFilter" which is referenced in web.xml -->
<context:annotation-config/>
<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
<bean class="MyRedisHttpSessionConfig">
<property name="connectionFactory" ref="webTierRedisConnectionFactory"/>
</bean>
<bean id="webTierRedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${service-tier:redisSentinelMasterName}"/>
<property name="port" value="${service-tier:redisSentinelHostPortCsv}"/>
<property name="usePool" value="true"/>
<property name="poolConfig">
<bean class="redis.clients.jedis.JedisPoolConfig">
<property name="maxWaitMillis" value="5000"/>
<property name="maxTotal" value="50"/>
<property name="maxIdle" value="5"/>
<property name="minIdle" value="1"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="numTestsPerEvictionRun" value="10"/>
</bean>
</property>
</bean>
...
Ivy Dependencies include:
<!-- these are for redis httpsession -->
<dependency org="redis.clients" name="jedis" rev="2.8.1"/>
<dependency org="org.springframework.data" name="spring-data-redis" rev="1.7.4.RELEASE"/>
<dependency org="org.springframework.data" name="spring-data-keyvalue" rev="1.1.4.RELEASE"/>
<dependency org="org.springframework.session" name="spring-session" rev="1.2.2.RELEASE"/>
and wicket 7.5.0 and spring 4.2.8 stuff.
Anybody have any insights on what might be going on? Why after putting the session into redis (which it is showing up there, I see it (via redis-cli and keys and dump commands), most ajax calls are triggering full page redirects due to response headers from the ajax call including Ajax-Location?
This turned out to be caused by a custom org.apache.wicket.IPageManagerProvider implementation which needed to be replaced with a version that was compatible with redis.
Here is the code I have for redirecting unmapped requests to 404 page
#RequestMapping("/**")
public ModelAndView redirect() {
ModelAndView mv = new ModelAndView();
mv.setViewName("errorPage");
return mv;
}
The above code works fine but the problem is with the web resources like css and js files
also go inside this redirect method and it is not loading any of the files. But i already have this code in my dispatcher servlet,but spring controller is not recognizing this resources mapping.
<mvc:resources mapping="/resources/**" location="/WEB-INF/web-resources/" />
so i tried some regex expression in the request mapping to negate the resources url something like this
#RequestMapping("/{^(?!.*resources/**)}**")
public ModelAndView redirect() {
ModelAndView mv = new ModelAndView();
mv.setViewName("errorPage");
return mv;
}
But this is not working as expected ..So if anyone could help it would be great :)
I found the solution to handle 404 (Unmapped links), I used a SimpleUrlHandlerMapping to do this.
I added the below code to my dispatcher servlet .xml
<!-- Here all your resources like css,js will be mapped first -->
<mvc:resources mapping="/resources/**" location="/WEB-INF/web-resources/" />
<context:annotation-config />
<!-- Next is your request mappings from controllers -->
<context:component-scan base-package="com.xyz" />
<mvc:annotation-driven />
<!-- Atlast your error mapping -->
<bean id="errorUrlBean" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/**">errorController</prop>
</props>
</property>
</bean>
<bean id="errorController" class="com.xyz.controller.ErrorController">
</bean>
com.xyz.controller.ErrorController class
public class ErrorController extends AbstractController {
#Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
// TODO Auto-generated method stub
ModelAndView mv = new ModelAndView();
mv.setViewName("errorPage");
return mv;
}
}
I found the below reasons
#RequestMapping("/**") uses RequestHandlerMapping and
<mvc:resources mapping="/resources/**" location="/WEB-INF/web-resources/" />
uses SimpleUrlHandlerMapping
RequestHandlerMapping takes presedence over SimpleUrlHandlerMapping, so that was the reason all resources request went inside the redirect method in my case.
So i just changed the #RequestMapping("/**") request to SimpleUrlHandlerMapping by configuring that as a bean as given above in my dipatcher servlet and mapped it at the last and it solved the problem.
Also add the below code to your web.xml
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/error.jsp</location>
</error-page>
Now this simple solution can be used to redirect all unmapped requests i.e) 404 errors to an error page :)
I'm new to Spring. I've recently learned to use Spring 3.1 to implement LDAP authentication. I was told to customize the authentication such that if a user from an IP address in the database comes to the site that s/he will be automatically logged in( not my idea, I was ordered to do it ). If not, that person will be sent to a login screen to do LDAP authentication
I made a skeleton of a custom PreAuthenticationFilter to check such users out, determine if they are from one of the annointed IP addresses and then send them to the rest of the authentication process.
I'm brand spanking new to Spring. So I cobbled this together from examples and reading over the reference many times.
When I try running it I get an error message that seems to me I am not setting up my tags properly or that I am leaving a big piece out ( do I need a second, custom, AuthenticationEntry point?)
Any tips in getting past this error would be appreciated:
the excerpt from my log
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'org.springframework.security.filterChains':
Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0'
while setting bean property 'sourceList' with key [0];
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0':
Cannot resolve reference to bean 'customFilter'
while setting constructor argument with key [2];
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'customFilter'
defined in ServletContext resource [/WEB-INF/acme-security.xml]:
Invocation of init method failed; nested exception is java.lang.IllegalArgumentException:
An AuthenticationManager must be set
My customized PreAuthenticatedFilter class for logging in by IP Address:
package com.acme.controller.security;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.apache.log4j.Logger;
public class CustomIPAddressAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
private static final Logger logger = Logger.getLogger(CustomIPAddressAuthenticationFilter.class);
private AuthenticationDetailsSource ads = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager;
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
boolean isAuthenticatedByIP = false;
// Get the IP address of the user tyring to use the site
String userIPAddress = request.getRemoteAddr();
logger.debug("userIPAddress == " + userIPAddress);
// Compare the user's IP Address with the IP address in the database
// stored in the USERS_AUTHENTICATED_BY_IP table & joined to the
// USERS tabe to make sure the IP Address has a current user
//isAuthenticatedByIP = someDataObject.hasIPAddress(userIPAddress);
isAuthenticatedByIP = true;
if(isAuthenticatedByIP){
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken("John.Principal","Password");
token.setDetails(ads.buildDetails(request));
try{
logger.debug("Going to set Authentication");
authentication = authenticationManager.authenticate(token);
// Setup the security context, aka authenticate
SecurityContextHolder.getContext().setAuthentication(authentication);
logger.debug("Authenticated");
}
catch(AuthenticationException e){
logger.debug("Authentication information was rejected by the authentication manager \n",e);
failureHandler.onAuthenticationFailure((HttpServletRequest)request, (HttpServletResponse)response, e);
}
}// end if(isAuthenticatedByIP)
}// end if(authenication == null)
chain.doFilter(request, response);
}// end function doFilter();
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
this.failureHandler = failureHandler;
}
protected String getPreAuthenticatedPrincipal(javax.servlet.http.HttpServletRequest request){
return "Joe.IP.User";
}
protected String getPreAuthenticatedCredentials(javax.servlet.http.HttpServletRequest request){
return "Password2";
}
}
My *-security.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:s="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<s:http auto-config="true" use-expressions="true">
<s:intercept-url pattern="/login*" access="hasRole('ROLE_ANONYMOUS')"/>
<s:intercept-url pattern="/search*" access="hasRole('ROLE_ANONYMOUS')"/>
<s:intercept-url pattern="/css/**" access="hasRole('ROLE_ANONYMOUS')"/>
<s:intercept-url pattern="/js/**" access="hasRole('ROLE_ANONYMOUS')"/>
<s:intercept-url pattern="/images/**" access="hasRole('ROLE_ANONYMOUS')"/>
<s:intercept-url pattern="/jsp/test*" access="hasRole('ROLE_ANONYMOUS')"/>
<s:intercept-url pattern="/**" access="isAuthenticated()" />
<s:custom-filter position="PRE_AUTH_FILTER" ref="customFilter" />
<s:form-login login-page="/login"
authentication-failure-url="/loginfailed" />
<s:logout logout-success-url="/logout" />
</s:http>
<bean id="customFilter" class="com.acme.controller.security.CustomIPAddressAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<s:ldap-server url = "ldap://ldap-itc.smen.acme.com:636/o=acme.com"/>
<s:authentication-manager alias="authenticationManager">
<s:ldap-authentication-provider user-dn-pattern="uid={0},ou=People"/>
</s:authentication-manager>
</beans>
My web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>Acme</display-name>
<!--welcome-file-list>
<welcome-file>/login</welcome-file>
</welcome-file-list-->
<servlet>
<servlet-name>acme</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>acme</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Help Find The Spring Config Files -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/nsd-servlet.xml,
/WEB-INF/nsd-security.xml
</param-value>
</context-param>
<!-- Spring Security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Integrate A Legacy Screen Done With A Servlet -->
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>
com.legacy.HelloWorldServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/helloworldservlet</url-pattern>
</servlet-mapping>
</web-app>
I believe some part of your security configuration is inconsistent in the way you use s:http in several places in the configuration. I suggest trying the following configuration:
<s:http auto-config="true" use-expressions="true">
<s:intercept-url pattern="/login*" access="isAnonymous()" />
<s:intercept-url pattern="/search*" access="isAnonymous()" />
<s:intercept-url pattern="/css/**" access="isAnonymous()" />
<s:intercept-url pattern="/js/**" access="isAnonymous()" />
<s:intercept-url pattern="/images/**" access="isAnonymous()" />
<s:intercept-url pattern="/**" access="isAuthenticated()" />
<s:form-login login-page="/login"
authentication-failure-url="/loginfailed" />
<s:logout logout-success-url="/logout" />
</s:http>
You need to activate the authentication process regardless of hitting /login; so I suggest using an implementation of a pre-authenticated filter AbstractPreAuthenticatedProcessingFilter. Your filter will do the authentication using IP address and populate the Authentication object thus not redirecting to the login page anymore. The filter should be configured in the position PRE_AUTH_FILTER. You can find a similar use case implementation for GAE here at Spring Source blog.
In your implementation you need to eliminate the attribute authenticationManager, It is managed by the parent class.
I'm using Spring 3, and trying to set up a simple web-app using annotations to define controller mappings. This seems to be incredibly difficult without peppering all the urls with *.form or *.do
Because part of the site needs to be password protected, these urls are all under /secure. There is a <security-constraint> in the web.xml protecting everything under that root. I want to map all the Spring controllers to /secure/app/.
Example URLs would be:
/secure/app/landingpage
/secure/app/edit/customer/{id}
each of which I would handle with an appropriate jsp/xml/whatever.
So, in web.xml I have this:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/secure/app/*</url-pattern>
</servlet-mapping>
And in despatcher-servlet.xml I have this:
<context:component-scan base-package="controller" />
In the Controller package I have a controller class:
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
#Controller
#RequestMapping("/secure/app/main")
public class HomePageController {
public HomePageController() { }
#RequestMapping(method = RequestMethod.GET)
public ModelAndView getPage(HttpServletRequest request)
{
ModelAndView mav = new ModelAndView();
mav.setViewName("main");
return mav;
}
}
Under /WEB-INF/jsp I have a "main.jsp", and a suitable view resolver set up to point to this. I had things working when mapping the despatcher using *.form, but can't get anything working using the above code.
When Spring starts up it appears to map everything correctly:
13:22:36,762 INFO main annotation.DefaultAnnotationHandlerMapping:399 - Mapped URL path [/secure/app/main] onto handler [controller.HomePageController#2a8ab08f]
I also noticed this line, which looked suspicious:
13:25:49,578 DEBUG main servlet.DispatcherServlet:443 - No HandlerMappings found in servlet 'dispatcher': using default
And at run time any attempt to view /secure/app/main just returns a 404 error in Tomcat, with this log output:
13:25:53,382 DEBUG http-8080-1 servlet.DispatcherServlet:842 - DispatcherServlet with name 'dispatcher' determining Last-Modified value for [/secure/app/main]
13:25:53,383 DEBUG http-8080-1 servlet.DispatcherServlet:850 - No handler found in getLastModified
13:25:53,390 DEBUG http-8080-1 servlet.DispatcherServlet:690 - DispatcherServlet with name 'dispatcher' processing GET request for [/secure/app/main]
13:25:53,393 WARN http-8080-1 servlet.PageNotFound:962 - No mapping found for HTTP request with URI [/secure/app/main] in DispatcherServlet with name 'dispatcher'
13:25:53,393 DEBUG http-8080-1 servlet.DispatcherServlet:677 - Successfully completed request
So... Spring maps a URL, and then "forgets" about that mapping a second later? What is going on?
Thanks.
I have exactly the same problem as you. The way to set 'alwaysUseFullPath' is pretty straightforward. My conf file is as following:
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"
p:order="3" > <!-- a higher value meaning greater in terms of sorting. -->
<property name="alwaysUseFullPath" value="true" />
<property name="interceptors">
<list>
<ref local="myInterceptor" />
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="alwaysUseFullPath" value="true" />
</bean>
Ah. As usual, found the answer after posting the question :-)
Changing the RequestMapping annotation to just /main fixes the problem. The documentation is not very clear on how all this is specified.
Put something like this in your #Configuration class:
#Bean(autowire = Autowire.BY_TYPE)
public AnnotationMethodHandlerAdapter handlerAdapter(){
final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
annotationMethodHandlerAdapter.setAlwaysUseFullPath(true);
return annotationMethodHandlerAdapter;
}
I am trying to create an application using spring mvc and hibenate. I have been seeing the exception failed to lazily initialize a collection of role for nearly two days now :( The application runs fine if I eager load the collections. But I don't want it that way.
I tried implementing OpenSessionInViewFilter in web.xml but still the error persisted. I tried to extend OpenSessionInViewFilter and use my own filter, even now the problem remains unsolved. Here is the filter I implemented
public class HibernateFilter extends OpenSessionInViewFilter {
#Override
protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
Session session = super.getSession(sessionFactory);
session.setFlushMode(FlushMode.AUTO);
return session;
}
#Override
protected void closeSession(Session session, SessionFactory sessionFactory) {
try {
if (session != null && session.isOpen() && session.isConnected()) {
try {
session.flush();
} catch (HibernateException e) {
throw new CleanupFailureDataAccessException("Failed to flush session before close: " + e.getMessage(), e);
} catch (Exception e) {
}
}
} finally {
super.closeSession(session, sessionFactory);
}
}
}
I ran the application in debug mode. I find the session to be not null, and the closeSession gets invoked only after it passes through the controller code. But still if I try to fetch a collection in the controller when the session is open it fails :( Here is my web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/blog-servlet.xml</param-value>
</context-param>
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
core.HibernateFilter
</filter-class>
<init-param>
<param-name>sessionFactoryBeanName</param-name>
<param-value>mySessionFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>blog</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>blog</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
What is going wrong?
As #sfussenegger mentioned...I am using sessions to identify the current user. This is my controller code:
#RequestMapping("/show.htm")
public String show(ModelMap model, HttpSession session) {
User u = (User) session.getAttribute("currentUser");
model.addAttribute("user", u);
if (u.getBlogs() != null) {
List<Blog> blogs = new ArrayList<Blog>(u.getBlogs());
model.addAttribute("myBlogs", blogs);
}
return "show";
}
and my jsp iterates over the MYblogs I collected in the model
My controller code:
Integer uid = (Integer) session.getAttribute("currentUser");
User user = getUserDao().findById(uid);
model.addAttribute("user", user);
if (user.getBlogs() != null) {
List<Blog> blogs = new ArrayList<Blog>(user.getBlogs()); //fails here
model.addAttribute("myBlogs", blogs);
}
return "show";
}
exception raised:
StandardWrapperValve[try-blog]: PWC1406: Servlet.service() for servlet try-blog threw exception
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ValueObjects.User.blogs, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:365)
at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:108)
at org.hibernate.collection.PersistentSet.toArray(PersistentSet.java:194)
at java.util.ArrayList.<init>(ArrayList.java:131)
at Controllers.UserController.show(UserController.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doInvokeMethod(HandlerMethodInvoker.java:421)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:136)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:326)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:313)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:875)
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:734)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:847)
at org.apache.catalina.core.ApplicationFilterChain.servletService(ApplicationFilterChain.java:427)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:333)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:198)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:246)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:313)
at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:287)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:218)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:94)
at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:98)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:222)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:587)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1096)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:166)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:587)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1096)
at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:288)
at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:647)
at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.doProcess(DefaultProcessorTask.java:579)
at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.process(DefaultProcessorTask.java:831)
at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.executeProcessorTask(DefaultReadTask.java:341)
at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:263)
at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:214)
at com.sun.enterprise.web.connector.grizzly.TaskBase.run(TaskBase.java:265)
at com.sun.enterprise.web.connector.grizzly.ssl.SSLWorkerThread.run(SSLWorkerThread.java:106)
Dick is correct, controller class might be doing something funny.
Another point is make sure you have all the required jar in your classpath such as cglib or javaassist.
It might be easier to use springs openSessionInViewInterceptor instead of OpenSessionInViewFilter (since you are already using spring).
simply add the following on your blog-servlet.xml config (you can of course split this to multiple files)
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors"><list>
<ref bean="openSessionInViewInterceptor" />
</list></property>
</bean>
<bean id="openSessionInViewInterceptor" class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor">
<property name="entityManagerFactory"><ref local="entityManagerFactory"/></property>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="ha-admin" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:database="ORACLE" p:showSql="true" />
</property>
<property name="jpaPropertyMap">
<props>
<!-- Enable Hibernate statistics generation
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.provider_configuration_file_resource_path">/ehcache.xml</prop>
<prop key="hibernate.generate_statistics">true</prop>
-->
</props>
</property>
</bean>
You need to add datasource on the configuration file and remove hibernateFilter from your web.xml.
Most likely you are using detached objects, i.e. object that have been loaded in a different session than the one created by your OpenSessionInViewFilter. This might happen when you store objects in the session and access them from a subsequent request. Is this possible?
Edit:
I'd discourage keeping the User object in the session. Instead, only keep the id and fetch the User object from DB each time it's needed. Hence, instead of your current approach, you should use something like this
User getUserFromSession(HttpSession session) {
Integer userId = (Integer) session.getAttribute("currentUser");
return userId != null ? getObjectById(User.class, userId) : null;
}
Note that fetching an object by id is insanely fast, especially if you've configured Hibernate's second level cache to store User object - so forget about any performance considerations. The main advantage though is that you don't have to deal with detached objects anymore. Detached objects are evil and nobody likes them! ;)
And as #skaffman mentioned, go back to the default OpenSessionInViewFilter as your implementation obviously won't solve your problem.
Hmmm, probably you are not going to change your ORM at this stage - but it is a nice reminder of why "Session-less" ORM's are interesting.
e.g. Ebean ORM ... no sessions, lazy loading just works (no filter required) - means you never hit this issue.
Just an idea: for me it helped to change the order of my filters. I had a url rewrite filter and the order with the session filter was important.
Just a thought...
If your code is failing in the controller, then the Filter is a red herring. Your Hibernate session should still be open at this point, filter or no filter.
Could you post your DAO code and the associated config?
You said that your application runs fine when you eagerly load the collection object.
Since you are using user object from the httpsession from another request, you might have to reattach the user object using refresh or update.
does the following code work?
User u = (User) session.getAttribute("currentUser");
User newUser = service.getUser(u.getUserId);
System.out.println(newUser.getBlogs());
And see if this helps. could you also list the jar files in your classpath.
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value> <!-- or false -->
</init-param>
<init-param>
<param-name>sessionFactoryBeanName</param-name>
<param-value>sessionFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>