I am trying to configure a web app to use Spring 3.2 + Spring Security + Apache Tiles + Apache Thymeleaf but i keep getting 404 error even though the logs (catalina.out) show no errors. What could be wrong with my configuration?? My configuration is as follows:
@Configuration
public class ThymeleafConfig {
@Bean
public BeanNameViewResolver beanViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(1);
return resolver;
}
@Bean
public MarshallingHttpMessageConverter marshallingMessageConverter() {
MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter(
jaxb2Marshaller(),
jaxb2Marshaller()
);
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.APPLICATION_XML);
converter.setSupportedMediaTypes(mediaTypes);
return converter;
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(stringHttpMessageConverter());
converters.add(marshallingJsonConverter());
converters.add(marshallingMessageConverter());
restTemplate.setMessageConverters(converters);
return restTemplate;
}
@Bean
public StringHttpMessageConverter stringHttpMessageConverter() {
return new StringHttpMessageConverter();
}
@Bean
public MappingJacksonHttpMessageConverter marshallingJsonConverter() {
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.APPLICATION_JSON);
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setSupportedMediaTypes(mediaTypes);
return converter;
}
@Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan(new String[]{
"org.tutiworks.orm.*"
});
return marshaller;
}
@Bean
public ThymeleafTilesConfigurer tilesConfigurer() {
ThymeleafTilesConfigurer tilesConfigurer = new ThymeleafTilesConfigurer();
tilesConfigurer.setDefinitions(new String[]{"/WEB-INF/tiles.xml"});
return tilesConfigurer;
}
@Bean
public TilesDialect tilesDialect() {
TilesDialect dialect = new TilesDialect();
return dialect;
}
@Bean
public SpringSecurityDialect springSecurityDialect() {
SpringSecurityDialect dialect = new SpringSecurityDialect();
return dialect;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
Set<IDialect> dialects = new HashSet<IDialect>();
dialects.add(springSecurityDialect());
dialects.add(tilesDialect());
engine.setAdditionalDialects(dialects);
return engine;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setViewClass(ThymeleafTilesView.class);
resolver.setOrder(2);
resolver.setCharacterEncoding("UTF-8");
String[] views = {"*.html", "*.xhtml"};
resolver.setViewNames(views);
return resolver;
}
/**
* Create the CNVR. Get Spring to inject the ContentNegotiationManager
* created by the configurer (see previous method).
*/
@Bean
public ViewResolver contentNegotiatingViewResolver(
ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
List<ViewResolver> viewResolvers = new ArrayList<ViewResolver>();
viewResolvers.add(beanViewResolver());
viewResolvers.add(thymeleafViewResolver());
resolver.setViewResolvers(viewResolvers);
return resolver;
}
}
@Configuration
@ComponentScan(basePackages = {"org.tutiworks"})
@EnableWebMvc
@Import({SpringDataConfig.class, ThymeleafConfig.class, SecurityConfig.class})
@PropertySource("classpath:spring.properties")
public class ApplicationContext extends WebMvcConfigurerAdapter {
// Maps resources path to webapp/resources
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations(
"/resources/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}
/**
* Setup a simple strategy: 1. Only path extension is taken into account,
* Accept headers are ignored. 2. Return HTML by default when not sure.
*/
@Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configurer.ignoreAcceptHeader(true);
Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
mediaTypes.put("xml2", MediaType.TEXT_XML);
mediaTypes.put("xhtml", MediaType.APPLICATION_XHTML_XML);
mediaTypes.put("html", MediaType.TEXT_HTML);
configurer.mediaTypes(mediaTypes);
configurer.defaultContentType(MediaType.TEXT_HTML);
}
// Only needed if we are using @Value and ${...} when referencing properties
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySources =
new PropertySourcesPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[]{new ClassPathResource(
"spring.properties")};
propertySources.setLocations(resources);
propertySources.setIgnoreUnresolvablePlaceholders(true);
return propertySources;
}
// Provides internationalization of messages
@Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource source = new ReloadableResourceBundleMessageSource();
source.setBasename("messages");
source.setDefaultEncoding("UTF-8");
return source;
}
}
public class ApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Load application context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(ApplicationContext.class);
rootContext.setDisplayName("Citizen Police");
rootContext.setConfigLocation("org.tutiworks.config");
FilterRegistration.Dynamic securityFilter = servletContext.addFilter("securityFilter",
new DelegatingFilterProxy("springSecurityFilterChain"));
securityFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
FilterRegistration.Dynamic characterEncodingFilter = servletContext.addFilter
("characterEncodingFilter", new CharacterEncodingFilter());
characterEncodingFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class),
true, "/*");
characterEncodingFilter.setInitParameter("encoding", "UTF-8");
characterEncodingFilter.setInitParameter("forceEncoding", "true");
servletContext.setInitParameter("defaultHtmlEscape", "true");
// Context loader listener
servletContext.addListener(new ContextLoaderListener(rootContext));
// Dispatcher servlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
"dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.setAsyncSupported(true);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/**").authenticated()
.antMatchers("/resources/**", "/forgot.html", "/signup.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.permitAll()
.defaultSuccessUrl("/home.html")
.failureUrl("/login.html?authfailed=true")
.and()
.logout()
.invalidateHttpSession(true)
.logoutUrl("/logout.html")
.deleteCookies("JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE")
.logoutSuccessUrl("/");
}
@Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
@Bean
public UserDetailsService loginService() {
UserDetailsService service = new LoginService();
return service;
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication().and()
.eraseCredentials(true)
.userDetailsService(loginService())
.passwordEncoder(passwordEncoder());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN"
"http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
<definition name="indexLayout" extends="standardLayout" template="/WEB-INF/templates/layouts/template.html" templateType="thymeleaf">
<put-attribute name="title" value="Afrikana - Sign in ::title" type="thymeleaf" />
<put-attribute name="head" value="/WEB-INF/templates/fragments/head.html ::head" type="thymeleaf" />
<put-attribute name="footer" value="/WEB-INF/templates/fragments/footer.html ::footer" type="thymeleaf" />
</definition>
<!-- Index page -->
<definition name="login" extends="indexLayout">
<put-attribute name="content" value="/WEB-INF/templates/pages/login.html :: content" type="thymeleaf" />
</definition>
<!--
<definition name="configure"
template="/WEB-INF/pages/configuration/configureDhis2.jsp" />
-->
</tiles-definitions>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
<head tiles:fragment="head">
<title tiles:string="title">
</title>
</head>
<body>
<div tiles:fragment="content"></div>
<div tiles:fragment="footer"></div>
</body>
</html>
What could be wrong? Why am I getting the 404 error when the app tries to load login.html?
You haven't set a url mapping for your Servlet.
// Dispatcher servlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.setAsyncSupported(true);
// add this
dispatcher.addMapping("/"); // or whatever you want/need