Spring RequestMapping from Resources and PropertyPlaceholderConfigurer? - java

I'm trying to do a RequestMapping for URL from the Resources file to be variable according to the current Locale
I tried to use the PlaceHolders but i know it should load from Properties files . in addition to i have to load it as Bean during the run time thus it will load one time only with the default Locale so even if the changed the Locale , it will keep loading from the default Locale > en_US
Any Ideas ?
My Tries :
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
setProperties(convertResourceBundleToProperties(ResourceBundle.getBundle("urls", LocaleContextHolder.getLocale())));
super.postProcessBeanFactory(beanFactory);
}
}
and calling at in a Bean :
#Bean
public CustomPropertyPlaceholderConfigurer CustomPropertyPlaceholderConfigurer(){
return new CustomPropertyPlaceholderConfigurer();
}
Resources urls_ab.properties:
url.controller1=test
Controller :
#RequestMapping(value = "/${url.controller1}", method = RequestMethod.GET)
public String dd(ModelMap model){
return "__front_container";
}

When you make a change to your properties files that back your PropertyPlaceholderConfigurer, you will need to 'refresh' your application for changes to take effect. If you use a ConfigurableApplicationContext as your context, then you may call refresh on your context. The challenge is that in a web application, you will depend on your web.xml and not directly on the context object so refreshing to load the new/updated properties would require an application restart...or going through many unnecessary hoops. Consider the below which is an example within a Spring Webflow application. The locale is updated via the use of an interceptor. :
public class MyLocaleChangeInterceptor extends org.springframework.web.servlet.i18n.LocaleChangeInterceptor {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
if (locale != null) {
try {
response.setLocale(locale);
} catch (Exception ex) {
response.setLocale(Locale.ENGLISH);
}
} else {
response.setLocale(Locale.ENGLISH);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
super.postHandle(request, response, handler, modelAndView);
}
}
/** https://gist.github.com/jkuipers/3537965 Spring LocaleResolver that uses cookies but falls back to the HTTP Session when cookies are disabled*/
public class MyCookieLocaleResolver extends CookieLocaleResolver {
private SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
#Override
protected Locale determineDefaultLocale(HttpServletRequest request) {
return sessionLocaleResolver.resolveLocale(request);
}
#Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
if (locale != null) {
try {
response.setLocale(locale);
} catch (Exception ex) {
response.setLocale(Locale.ENGLISH);
}
} else {
response.setLocale(Locale.ENGLISH);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
super.setLocale(request, response, locale);
sessionLocaleResolver.setLocale(request, response, locale);
}
#Override
public void setDefaultLocale(Locale defaultLocale) {
sessionLocaleResolver.setDefaultLocale(defaultLocale);
}
}
<!--Then the XML:-->
<bean id="localeChangeInterceptor" class="MyLocaleChangeInterceptor">
<property name="paramName" value="lang"/>
</bean>
<!-- Saves a locale change using a cookie -->
<bean id="localeResolver" class="MyCookieLocaleResolver" >
<property name="defaultLocale" value="en" />
</bean>
<!--Then Spring-webflow specific XML settings:-->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="order" value="2"/>
<property name="flowRegistry" ref="flowRegistry" />
<property name="interceptors" >
<list>
<ref local="localeChangeInterceptor" />
</list>
</property>
<property name="defaultHandler">
<bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
</property>
</bean>
If using Spring MVC (without spring webflow), see here for a brilliant solution: Spring MVC LocaleChangeInterceptor annotation based doesn't work
MyKong also provides a good solution: http://www.mkyong.com/spring-mvc/spring-mvc-internationalization-example/

Related

No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here without using #contoller

I'm newbie to spring framework.I have integrated spring + hibernate to create RESTFUI API web service.
public interface IGenericDao {
long persists(T clazz) throws ResourceFailureException;
List select(T clazz)throws ResourceFailureException;
}
#Repository(value="genericDao")
public class GenericDao implements IGenericDao {
#Autowired
SessionFactory sessionFactory;
#Override
public long persists(T clazz) throws ResourceFailureException {
long generatedID;
generatedID = (Long) getCurrentSession().save(clazz);
getCurrentSession().getTransaction().commit();
return generatedID;
}
#Override
public List select(T clazz) throws ResourceFailureException {
String queryStr = " FROM "+clazz.getClass().getName()+" as table WHERE (table.isDelete is null OR"
+ " table.isDelete = false) ";
return this.select(queryStr);
}
protected final Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
//Implement GenericDAO class
#Repository(value="roleCapabilityDao")
public class RoleCapabilityDAO extends GenericDao{
public final void persistRole(final Role role) throws ResourceFailureException {
persists(role);
}
public final List getRoles(final String whereString)
throws ResourceFailureException {
String queryStr = "FROM Role " + whereString;
return select(queryStr);
}
public final Role getRoleById(final int roleId) throws ResourceFailureException {
String whereString = "WHERE id=" + roleId;
List roles = getRoles(whereString);
if (roles != null && !roles.isEmpty()) {
return roles.get(0);
}
return null;
}
}
//Servlet-context class.
#Transactional
public class HibernateUtility {
private static Logger logger =Logger.getLogger(HibernateUtility.class.getName());
public HibernateUtility() {
}
#Autowired(required=true)
RoleCapabilityDAO roleCapabilityDao;
#Autowired(required=true)
UserDAO userDao;
public void createDefaultUser(final ServletContext context) {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, context);
Users user = new Users();
Role role = new Role();
.....
try {
role = roleCapabilityDao.getRoleByName("SystemAdmin");
}catch (ResourceFailureException re) {
logger.error("Resource not found" +re.getMessage());
}
}
applicationContext.xml
<tx:annotation-driven transaction-manager="transactionManager"/>
<context:annotation-config />
<context:component-scan base-package="com.base" use-default-filters="false"/>
<bean id="propertyConfigurer"
.....
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.databaseurl}" p:username="${jdbc.username}" p:password="${jdbc.password}">
<property name="testOnBorrow" value="true"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>HibernateUtility</servlet-name>
<servlet-class>com.base.hibernate.HibernateUtilityServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
//RESTFUL API calls
#Path("/roles")
#Service
#Transactional
public class RoleCapabilityResource {
public RoleCapabilityResource(){
super();
}
#Autowired
UserDAO userDao;
#Autowired
RoleCapabilityDAO roleCapabilityDao;
private static Logger roleLogger=Logger.getLogger(RoleCapabilityResource.class.getName());
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/getAllRoles")
#Loggable(value = LogLevel.DEBUG)
#CapabilityCode(value = "C_User_R")
public Response getAllRoles(#Context final HttpServletRequest request) {
HttpSession session = request.getSession();
try {
String loggedUser = session.getAttribute("userName").toString();
Users user = userDao.getCurrentUser(loggedUser.trim());
if(user == null){
return Response.status(Response.Status.BAD_REQUEST).type("text/plain").entity("Current User not found").build();
}
List<Role> roleList = roleCapabilityDao.getValidRolesForUserRole(user.getRole().getName(), false);
JSONObject allRolesJson = getRoleJSON(roleList);
return Response.status(Response.Status.OK).type(MediaType.APPLICATION_JSON).entity(allRolesJson).build();
} catch (ResourceFailureException re) {
roleLogger.error("Error in resource"+re.getMessage());
return Response.status(Response.Status.BAD_REQUEST).type("text/plain").entity(re.toString()).build();
}
}
}
//HibernateUtilizyServlet
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Hibernate Utility Servlet is invoked only once during JBoss Startup.
*/
#Service
#Transactional
public class HibernateUtilityServlet extends HttpServlet {
/**
* Default serial Version ID.
*/
private static final long serialVersionUID = 1L;
#Override
public void init(ServletConfig config) {
try {
super.init(config);
ServletContext context = getServletContext();
HibernateUtility hibernateUtil = new HibernateUtility();
hibernateUtil.createDefaultUser(context);
} catch (ServletException e) {
e.printStackTrace();
}
}
}
If i run the application it throws below error message
StandardWrapper.Throwable: org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:63) [:3.1.0.RELEASE]
at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:685) [:3.6.0.Final]
at com.base.dao.GenericDao.getCurrentSession(GenericDao.java:186) [:]
at com.base.dao.GenericDao.select(GenericDao.java:80) [:]
at com.base.dao.RoleCapabilityDAO.getRoles(RoleCapabilityDAO.java:29) [:]
at com.base.dao.RoleCapabilityDAO.getRoleByName(RoleCapabilityDAO.java:40) [:]
at com.base.hibernate.HibernateUtility.createDefaultUser(HibernateUtility.java:180) [:]
at com.base.hibernate.HibernateUtilityServlet.init(HibernateUtilityServlet.java:41) [:]
I have tried some links as mentioned below,
1.Spring MVC + Hibernate: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.
2.I am receiving HibernateException "No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here"
but I have not resolved yet.The only difference is here I didn't use #controller annotation.
How to resolve this Error?
How to set sessions handle by spring?
HibernateUtilityServlet is no Spring Bean! It is just some simple Http Servlet created by your Servlet container (not by Spring).
Therefore
your Spring annotations at HibernateUtilityServlet are ignored (they are only taken in account if your Object is an Spring Bean (created by Spring))
you can not inject something in HibernateUtilityServlet
Futuremore: when you create an instance with new (like you did HibernateUtility hibernateUtil = new HibernateUtility();), then this Object will be no Spring Bean. Therefore
(you already know it):
your Spring annotations at this class are ignored
you can not inject something in this object
I don't really know what you want do do with this HibernateUtilityServlet, but it looks like you try to setup the database when the application starts. -- A Much more easyer way would be using the spring default functionality for this:
#Component
public class OnStartupAction implements ApplicationListener<ContextStartedEvent> {
#Override
public void onApplicationEvent(final ContextStartedEvent event) {
// do whatever you need here
}
}
More details in this answer of mine.

Logout/Session timeout catching with spring security

I'm using spring/spring-security 3.1 and want to take some action whenever the user logs out (or if the session is timed out). I managed to get the action done for logout but for session timeout, I can't get it working.
In web.xml I only have the ContextLoaderListener specified ( can this be the issue? ) and of course the DelegatingFilterProxy.
I use the auto config like this.
<security:http auto-config="false" use-expressions="false">
<security:intercept-url pattern="/dialog/*"
access="ROLE_USERS" />
<security:intercept-url pattern="/boa/*"
access="ROLE-USERS" />
<security:intercept-url pattern="/*.html"
access="ROLE-USERS" />
<security:form-login login-page="/auth/login.html"
default-target-url="/index.html" />
<security:logout logout-url="/logout"
invalidate-session="true"
delete-cookies="JSESSIONID" success-handler-ref="logoutHandler" />
</security:http>
<bean id="logoutHandler" class="com.bla.bla.bla.LogoutHandler">
<property name="logoutUrl" value="/auth/logout.html"/>
</bean>
The logout handler is called when user clicks logout, which will make some calls to a database.
But how do I handle the session timeout ???
One way to handle it would be to inject the username into the session when user logs in and then use an ordinary httpsessionlistener and do the same thing on session timeout.
Is there a similar way with spring security, so that when spring discovers that the session is to timeout, I can hook in there, access the Authentication and get the UserDetails from there and do the clean up.
I've got a simpler solution. This works for both logout and session timeout.
#Component
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event)
{
List<SecurityContext> lstSecurityContext = event.getSecurityContexts();
UserDetails ud;
for (SecurityContext securityContext : lstSecurityContext)
{
ud = (UserDetails) securityContext.getAuthentication().getPrincipal();
// ...
}
}
}
web.xml:
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
Ok, I got a solution working, it's not as good as I'd like, but it get's me to the result.
I create a bean from which I can get a hold of the ApplicationContext.
public class AppCtxProvider implements ApplicationContextAware {
private static WeakReference<ApplicationContext> APP_CTX;
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
APP_CTX = new WeakReference<ApplicationContext>(applicationContext);
}
public static ApplicationContext getAppCtx() {
return APP_CTX.get();
}
}
I implement HttpSessionEventPublisher and on destroy, i get the UserDetails via sessionRegistry.getSessionInfo(sessionId)
Now I have the spring beans which I need to do the cleanup of the session and the user for whom the session timed out for.
public class SessionTimeoutHandler extends HttpSessionEventPublisher {
#Override
public void sessionCreated(HttpSessionEvent event) {
super.sessionCreated(event);
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
SessionRegistry sessionRegistry = getSessionRegistry();
SessionInformation sessionInfo = (sessionRegistry != null ? sessionRegistry
.getSessionInformation(event.getSession().getId()) : null);
UserDetails ud = null;
if (sessionInfo != null) {
ud = (UserDetails) sessionInfo.getPrincipal();
}
if (ud != null) {
// Do my stuff
}
super.sessionDestroyed(event);
}
private SessionRegistry getSessionRegistry() {
ApplicationContext appCtx = AppCtxProvider.getAppCtx();
return appCtx.getBean("sessionRegistry", SessionRegistry.class);
}
You can use SimpleRedirectInvalidSessionStrategy to redirect to a URL when an invalid requested session is detected by the SessionManagementFilter.
Sample applicationContext would be like this:
<http>
<custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
<http>
<beans:bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
<beans:constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
<beans:property name="invalidSessionStrategy" ref="simpleRedirectInvalidSessionStrategy " />
</beans:bean>
<beans:bean id="simpleRedirectInvalidSessionStrategy" class="org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/general/logins/sessionExpired.jsf" />
<beans:property name="createNewSession" value="false" />
</beans:bean>
<beans:bean id="httpSessionSecurityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/>
If you are using JSF, also refer to JSF 2, Spring Security 3.x and Richfaces 4 redirect to login page on session time out for ajax requests on how to handle Ajax requests as well.
UPDATE: In such a case, you can extend the HttpSessionEventPublisher and listen for sessionDestroyed events like this:
package com.examples;
import javax.servlet.http.HttpSessionEvent;
import org.springframework.security.web.session.HttpSessionEventPublisher;
public class MyHttpSessionEventPublisher extends HttpSessionEventPublisher {
#Override
public void sessionCreated(HttpSessionEvent event) {
super.sessionCreated(event);
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
//do something
super.sessionDestroyed(event);
}
}
and then register this listener in your web.xml like this:
<listener>
<listener-class>com.examples.MyHttpSessionEventPublisher</listener-class>
</listener>
While session getting created at the time of one microservice application with setMaxInactive interval to 60 sec suppose . Session gets timedout after 60sec but after using your suggested change it should come to sessionDestroyed event but still it gets under session created and session again doesnot gets invalidated.

Spring XML View Resolver Configuration

I am trying to output some model data to a pdf using spring-mvc. It is not working and I was wondering if someone could offer some advice.
I have a spring-servlet.xml file that includes the following:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="order" value="1"/>
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="xmlViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="order" value="2"/>
<property name="location">
<value>/WEB-INF/spring-pdf-views.xml</value>
</property>
</bean>
In the spring-pdf-views.xml file I have this:
<bean id="MyPDF" class="com.example.MyPDFView"/>
This is my MyPDFView class:
public class MyPDFView extends AbstractPdfView {
#Override
protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,
HttpServletRequest request, HttpServletResponse response) throws Exception {
#SuppressWarnings("unchecked")
Map<String, String> data = (Map<String, String>) model.get("modelData");
Table table = new Table(2);
table.addCell("Date");
table.addCell("Name");
table.addCell(data.get("modelData.dateValue"));
table.addCell(data.get("modelData.nameValue"));
document.add(table);
}
}
Finally in my controller I have:
#RequestMapping(value="/pdfInformation", method=RequestMethod.POST)
public ModelAndView showPDF(ModelMap model, PDFInfo pdfInfo, BindingResult result) {
return new ModelAndView("MyPDF", model);
}
The problem I am seeing in the output is that it never gets to the xmlViewResolver. It is trying to render the MyPDF as a JSTL View. This is from my logs:
org.springframework.web.servlet.DispatcherServlet - Rendering view [org.springframework.web.servlet.view.JstlView: name 'MyPDF'; URL [/WEB-INF/view/MyPDF.jsp]] in DispatcherServlet with name 'spring'
What am I missing?
From the Javadoc for InternalResourceViewResolver:
Note: When chaining ViewResolvers, an InternalResourceViewResolver always needs to be last, as it will attempt to resolve any view name, no matter whether the underlying resource actually exists.
Swap the order of your resolvers.

spring mvc forward to jsp

I currently have my web.xml configured to catch 404s and send them to my spring controller which will perform a search given the original URL request.
The functionality is all there as far as the catch and search go, however the trouble begins to arise when I try to return a view.
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver" p:order="1">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="jsp" value="text/html" />
</map>
</property>
<property name="defaultContentType" value="application/json" />
<property name="favorPathExtension" value="true" />
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value="" />
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
</list>
</property>
<property name="ignoreAcceptHeader" value="true" />
</bean>
This is a snippet from my MVC config file.
The problem lies in resolving the view's path to the /WEB-INF/jsp/ directory. Using a logger in my JBoss setup, I can see that when I test this search controller by going to a non-existent page, the following occurs:
Server can't find the request
Request is sent to 404 error page (in this case my search controller)
Search controller performs search
Search controller returns view name (for this illustration, we'll assume test.jsp is returned)
Based off of server logger, I can see that org.springframework.web.servlet.view.JstlView is initialized once my search controller returns the view name (so I can assume it is being picked up correctly by the InternalResourceViewResolver)
Server attempts to return content to browser resulting in a 404!
A couple things confuse me about this:
I'm not 100% sure why this isn't resolving when test.jsp clearly exists under the /WEB-INF/jsp/ directory.
Even if there was some other problem, why would this result in a 404? Shouldn't a 404 error page that results in another 404 theoretically create an infinite loop?
Thanks for any help or pointers!
Controller class [incomplete]:
#Controller
public class SiteMapController {
//--------------------------------------------------------------------------------------
#Autowired(required=true)
private SearchService search;
#Autowired(required=true)
private CatalogService catalog;
//--------------------------------------------------------------------------------------
#RequestMapping(value = "/sitemap", method = RequestMethod.GET)
public String sitemap (HttpServletRequest request, HttpServletResponse response) {
String forwardPath = "";
try {
long startTime = System.nanoTime() / 1000000;
String pathQuery = (String) request.getAttribute("javax.servlet.error.request_uri");
Scanner pathScanner = new Scanner(pathQuery).useDelimiter("\\/");
String context = pathScanner.next();
List<ProductLightDTO> results = new ArrayList<ProductLightDTO>();
StringBuilder query = new StringBuilder();
String currentValue;
while (pathScanner.hasNext()) {
currentValue = pathScanner.next().toLowerCase();
System.out.println(currentValue);
if (query.length() > 0)
query.append(" AND ");
if (currentValue.contains("-")) {
query.append("\"");
query.append(currentValue.replace("-", " "));
query.append("\"");
}
else {
query.append(currentValue + "*");
}
}
//results.addAll(this.doSearch(query.toString()));
System.out.println("Request: " + pathQuery);
System.out.println("Built Query:" + query.toString());
//System.out.println("Result size: " + results.size());
long totalTime = (System.nanoTime() / 1000000) - startTime;
System.out.println("Total TTP: " + totalTime + "ms");
if (results == null || results.size() == 0) {
forwardPath = "home.jsp";
}
else if (results.size() == 1) {
forwardPath = "product.jsp";
}
else {
forwardPath = "category.jsp";
}
}
catch (Exception ex) {
System.err.println(ex);
}
System.out.println("Returning view: " + forwardPath);
return forwardPath;
}
}
1 . I'm not 100% sure why this isn't resolving when test.jsp clearly exists under the /WEB-INF/jsp/
directory.
This is because you did configure your view resolver with suffix = "" so the file must be named test (no extension).
2 . Even if there was some other problem, why would this result in a 404? Shouldn't a 404 error page that
results in another 404 theoretically create an infinite loop?
I'm pretty sure this's the result of some protection against the infinite redirect loop in spring MVC.
Note: in controllers, spring expect a view name as a result so test not test.jsp (or better, use ModelAndView)
I am posting this as answer because it is too long but it is probably not an answer.
http://localhost:8080/webapp/servlet-mapping-url/controller-mapping/method-mapping
if your controller's method which handles the request does not return a view name string or a view object or write directly to output stream, spring dispatcher should resolve the view name to /WEB-INF/jsp/controller-mapping/method-mapping.jsp
This means the jsp must be under a folder, named /WEB-INF/jsp/controller-mapping/. However, if a view name or view object is return by the controller method, spring dispatcher will uses that instead.
Ther are other many possible mapping combination but this is the most common one. If you could show your controller class, it will be easier.
Update
If you are using DefaultAnnotationHandlerMapping, you should always annotated your class with #RequestMapping(value = "/mapping-string"). Otherwise spring dispatcher will try to pick it up when nothing else is matched.
Since the controller is mapped, you will have to change the method mapping to value = {"", "/"}
For the returning view name, you don't need to put .jsp.
If the returning view name is home then the spring dispatcher will resolve to /WEB-INF/jsp/home.jsp
If the returning view name is path/home then the spring dispatcher will resolve to /WEB-INF/jsp/path/home.jsp
P.S. You used a word forwardPath but it isn't really a forward. It is just a view name.
#Controller
#RequestMapping(value = "/sitemap")
public class SiteMapController {
#RequestMapping(value = {"", "/"}, method = RequestMethod.GET)
public String sitemap (HttpServletRequest request, HttpServletResponse response) {
...
if (results == null || results.size() == 0) {
forwardPath = "home";
}
else if (results.size() == 1) {
forwardPath = "product";
}
else {
forwardPath = "category";
}
...
return forwardPath;
}
}
Try to set order for your ViewResolver. For Example:
#Bean
public ViewResolver getViewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
resolver.setOrder(1);
return resolver;
}

Using Velocity Tools with Spring 3.0.3

When I update the bean:
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".vm"/>
<property name="toolboxConfigLocation" value="tools.xml" />
</bean>
With the tools.xml path for Velocity Tools, I get:
Caused by:
java.lang.ClassNotFoundException: org.apache.velocity.tools.view.ToolboxManager
I've tried plugging in tools version 2 and 1.4, neither have this package structure. Did I miss something obvious? What version of Velocity Tools is the Spring/Velocity component supporting?
I use a little bit simpler of a way. I also cannot force Velocity Tools to work due to lack of configuration documentation and examples. I just get the velocity-generic-tools-2.0.jar and make a little change in my view resolver:
<bean id="velocityViewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="order" value="1"/>
<property name="prefix" value="/WEB-INF/vm/"/>
<property name="suffix" value=".vm"/>
<property name="exposeSpringMacroHelpers" value="true"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
<property name="attributesMap">
<map>
<!--Velocity Escape Tool-->
<entry key="esc"><bean class="org.apache.velocity.tools.generic.EscapeTool"/></entry>
</map>
</property>
</bean>
Then, in the velocity template you can use it as usual $esc.html($htmlCodeVar). This solution is very simple, without tons of configs and overriding spring classes.
Spring has very outdated Velocity support by default. I extend VelocityView class from Spring and override createVelocityContext method where I initialize Tools myself. Here is how it looks at the end.
With 3.0.5 I used a similar class to what serg posted, with the only modification being to use the updated classes which spring did not use (tail through VelocityToolboxView -> ServletToolboxManager (used in the createVelocityContext we have overridden) That is the class which is deprecated, so I modified the initVelocityToolContext in serg's answer to be:
private ToolContext getToolContext() throws IllegalStateException, IOException {
if (toolContext == null) {
XmlFactoryConfiguration factoryConfiguration = new XmlFactoryConfiguration("Default Tools");
factoryConfiguration.read(getServletContext().getResourceAsStream(getToolboxConfigLocation()));
ToolboxFactory factory = factoryConfiguration.createFactory();
factory.configure(factoryConfiguration);
toolContext = new ToolContext();
for (String scope : Scope.values()) {
toolContext.addToolbox(factory.createToolbox(scope));
}
}
return toolContext;
}
I also had to change the line which created the VelocityContext to call this method obviously.
My bean now looks like:
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver"
p:cache="false"
p:prefix=""
p:suffix=".vm"
p:layoutUrl="templates/main.vm"
p:toolboxConfigLocation="/WEB-INF/velocity/velocity-toolbox.xml"
p:viewClass="path.to.overriden.class.VelocityToolsLayoutView"
/>
Inspired by answers from Scott and serg, here's another way to do it that does not require XML: http://squirrel.pl/blog/2012/07/13/spring-velocity-tools-no-xml/
public class MyVelocityToolboxView extends VelocityView {
#Override
protected Context createVelocityContext(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) {
ViewToolContext context = new ViewToolContext(getVelocityEngine(),
request, response, getServletContext());
ToolboxFactory factory = new ToolboxFactory();
factory.configure(ConfigurationUtils.getVelocityView());
for (String scope : Scope.values()) {
context.addToolbox(factory.createToolbox(scope));
}
if (model != null) {
for (Map.Entry<String, Object> entry : (Set<Map.Entry<String, Object>>) model
.entrySet()) {
context.put(entry.getKey(), entry.getValue());
}
}
return context;
}
}
Inspired by all the answers above, this is my implementation of VelocityLayoutView for spring and velocity-tools 2.0, added some improvement!
public class VelocityToolsView extends VelocityLayoutView {
private static final String TOOL_MANAGER_KEY = ViewToolManager.class.getName();
#Override
protected Context createVelocityContext(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
ServletContext application = getServletContext();
// use a shared instance of ViewToolManager
ViewToolManager toolManager = (ViewToolManager)application.getAttribute(TOOL_MANAGER_KEY);
if(toolManager == null) {
toolManager = createToolManager(getVelocityEngine(), getToolboxConfigLocation(), application);
application.setAttribute(TOOL_MANAGER_KEY, toolManager);
}
ViewToolContext toolContext = toolManager.createContext(request, response);
if(model != null) { toolContext.putAll(model); }
return toolContext;
}
private ViewToolManager createToolManager(VelocityEngine velocity, String toolFile, ServletContext application) {
ViewToolManager toolManager = new ViewToolManager(application, false, false);
toolManager.setVelocityEngine(velocity);
// generic & view tools config
FactoryConfiguration config = ConfigurationUtils.getVelocityView();
// user defined tools config
if(toolFile != null) {
FactoryConfiguration userConfig = ConfigurationUtils.load(application.getRealPath(toolFile));
config.addConfiguration(userConfig);
}
toolManager.configure(config);
return toolManager;
}
}
I found that this variation on #serg's technique worked for me.

Categories