How to map all calls to *.html in a springboot controller? - java

I am trying to serve dynamic html pages with Thymeleaf template and springboot api. This is the scenario that I want to achieve.
When someone makes the following request: hostname/client then the application would return a Json object on the other hand if someone makes this request: hostname/client.html, this request is catch in a different controller so that I can manipulate the view that will be returned.
Client Controller
This class is working as expected, it is returning a Json Object
#RestController
public class ClientController {
#Autowired
public ClientService clientServiceImp;
#RequestMapping("/client")
public Client get(#RequestParam(value="name", defaultValue="World") String name){
return clientServiceImp.getClient(name);
}
}
Home Controller
This class's method does not map calls to *.html
#Controller
public class HomeController {
#RequestMapping(value={"/*.html"}, produces="text/html")
public String getIndex(Model model, HttpServletRequest request){
// I will set here the thymeleaf fragment location based on the resource requested.
return "index";
}
*This is the error I am receiving after calling hostname/client.html
Whitelabel Error Page
*This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Jan 25 16:04:56 BRST 2016
There was an unexpected error (type=Not Acceptable, status=406).
Could not find acceptable representation**
Springboot basic configuration
#SpringBootApplication(scanBasePackages = {"com.serviceira"})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
It is important to point that I did not set any other configuration for the application. Thanks in advance for the help.

The reason ClientController is working properly is because you marked it as a #RestController, you are telling Spring to write the response to the html page directly.
However, your HomeController is not finding the mapping because you have not set up the servlet mapping yet.
If you are in Java config:
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("*.html"); // HERE YOU ARE SETTING THE .html mapping
}
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("your package name here");
return context;
}
}
If you are using XML, besides setting up your servelt, you need a mapping like this:
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/servlet-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>

Problem Solved
I solved the problem by adding different "produces" argument for for each controller's RequestMapping annotation, besides to change the value from the getIndex from /.html to .html.
HomeController
#RequestMapping(value={"*.html"}, produces="text/html")
ClientController
#RequestMapping(value="/client" produces="application/json")
Now, every request that contains *.html is received and treated by the getIndex method from the HomeController class and the others are handled by its own controller. For example:
request: GET: localhost/client
Controller
#RequestMapping(value="/client" produces="application/json")
public Client get(#RequestParam(value="name", defaultValue="World") String name){
return clientServiceImp.getClient(name);
}
request: GET: localhost/client.html
Controller
#RequestMapping(value={"*.html"}, produces="text/html")
public String getIndex(Model model, HttpServletRequest request){
// I will set here the thymeleaf fragment location based on the resource requested.
return "index";
}

Related

SpringMVC RestEasy Integration Beans aren't injected to Controller classes

I'm working on a project which has spring mvc 2.5.* .I have integrated RESTeasy to create restful endpoints. I have defined a controller class with #Controler. I have a bean which is supposed to be injected to this controller but it seems it's not getting injected, thus it throws a null pointer exception when the bean variable is used in controller rest methods.
I have added these to the web.xml
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest</param-value>
</context-param>
<servlet>
<servlet-name>resteasy-servlet</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.test.package.controller.MyApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
Customized class I extended from resteasy Application class to define application path:
#ApplicationPath("/rest")
public class MyApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
// register root resource
classes.add(EmployeeController.class);
return classes;
}
}
I have a servlet.xml which has defined mvc controller beans which works fine:
eg:
<bean name="/testDetail.html" class="com.test.package.controller.TestDetailController" parent="baseController">
<property name="testService" ref="testService"/>
</bean>
My Controller class looks like this:
#Controller
#Path("/employee")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class EmployeeController {
private static final Log LOG = LogFactory.getLog(EmployeeController.class);
private EmployeeService employeeService; // This doesn't get initiated.
#PUT
#Path("/update/{empId}")
public Response updateEmp(#PathParam("empId") Long empId, EmpRequest request) {
Response response = null;
employeeService.update(request); // NULL POINTER THROWN HERE, employeeService is null
return response;
}
public EmployeeService getEmployeeService () {
return employeeService ;
}
public void serEmployeeService (EmployeeService employeeService ) {
this.employeeService = employeeService ;
}
}
Here I added another bean to servlet.xml as,
<bean id="employeeController " class="com.test.package.controller.EmployeeController">
<property name="employeeService " ref="employeeService "/>
</bean>
In a separate xml employeeService bean is defined. It' just that it won't reflect in the employeeController bean. I'm not sure if even employeeController bean is created or not. But I'm able to call the endpoint successfully. bean initiation works fine if put employeeService bean in a MVC controller. eg:
<bean name="/testDetail.html" class="com.test.package.controller.TestDetailController" parent="baseController">
<property name="employeeService " ref="employeeService "/>
</bean>
But not in RestController. Any Idea on this behavior. Am I missing any configuration here.
firstly, you should define the employeeService bean as follows,
<bean id="employeeService" class="com.test.package.service.EmployeeService">
--------
</bean>
load the above config xml file in web.xml,
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/XML_FILE_PATH/XML_FILE_NAME.xml</param-value>
</context-param>
then, you can use employeeService bean
use #PostConstruct annotation for invoke with this method after employeeService bean is initialized
#PUT
#PostConstruct
#Path("/update/{empId}")
public Response updateEmp(#PathParam("empId") Long empId, EmpRequest request) {
Response response = null;
employeeService.update(request); // NULL POINTER THROWN HERE, employeeService is null
return response;
}

Spring MVC URI mapping configuration

I use WebApplicationInitializer to initialize Spring Dispatcher, this is how it was set.
public class MyApplicationInitializer implements WebApplicationInitializer{
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
final XmlWebApplicationContext appconfig = new XmlWebApplicationContext();
appconfig.setConfigLocation("/WEB-INF/app-servlet.xml");
final ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("my_app", new DispatcherServlet(appconfig));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/app/*.do");
System.out.println("Here I am ");
}
and I intention is to use controller class as namespace as
#Controller
#RequestMapping("app")
public class TestingController{
#RequestMapping("/{act}.do")
public ModelAndView getIndex(){ return new ModelAndView("/index.jsp"); }
}
it seems reasonable, but when I try to run this artifact from tomcat I got this error message.
Caused by: java.lang.IllegalArgumentException: Invalid <url-pattern> /app/*.do in servlet mapping
Question is why i can't define dispatching URI like this and if I want to achieve class URI + method URI for a dispatcher how should I set it?
It looks like your endpoints are formulated for struts. Spring does not support this. Probably because it is a non-standard REST technique.
The more RESTful way of doing this would be to design the URI like this:
/app/do/{action}
Or:
/app/do?action={action}
However, you can use regex PathVariables like this:
/app/{action:[A-Za-z\\d]+}.do
Then, within the method block evaluate the request path and redirect to different controllers conditionally based on pattern matching.
Here is the example I was able to get working:
package com.app.controller;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
#Controller
#RequestMapping(value = "/test")
public class TestController
{
#RequestMapping(value = "/{action:[A-Za-z\\d]+}.do")
public #ResponseBody
ModelAndView getDo(#PathVariable String action)
{
return new ModelAndView("redirect:/test/action/" + action);
}
#RequestMapping(value = "/action/a")
public #ResponseBody
ModelAndView getAction()
{
return new ModelAndView("/index.jsp");
}
}
The getAction method could be in another controller if you wanted it to, but by using regex and redirect I think you can accomplish what you are trying to do.

How to configure and load spring context in servlet project

I am pretty new to spring. I have dynamic web application project. Inside i have a servlet which is receive request. The request comes with a request no. Based on the no i will create a new object for appropriate request class and serve the request. Now i need to integrate with spring. i applied the below configuration,
WEB.XML (To load spring context)
<servlet>
<servlet-name>spring-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/docspring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
In servlet
package com.receve;
#Controller
#WebServlet("/Recever")
public class Recever extends HttpServlet {
#Autowired
private ClassOne one;
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
//request parameters receved and
if(req==1){
one.print();
}
}
public ClassOne getOne() {
return One;
}
public void setOne(ClassOne one) {
this.one = one;
}
}
In ClassOne
package com.operations;
#Component
public Class ClassOne{
public void print(){
//some statement;
}
}
In spring.xml
<context:annotation-config />
<context:component-scan base-package="com" />
But while running the application i am getting NullPointerexception while calling one.print() method. what is the proper way(configuration) to obtain this?
Thanks.
Remove extends HttpServlet and Autowiring is turned off by default, so the default autowiring mode for a bean is 'no'.

Get ServletContext in Application

Could you possibly explain how I can get the ServletContext instance in my Application's sub-class? Is it possible? I have tried to do it like in the following snippet but it does not seem to work - the ctx is not set:
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
//...
#ApplicationPath("/")
public class MainApplication extends Application {
#Context ServletContext ctx;
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
//...
return classes;
}
}
web.xml:
<web-app ...>
<context-param>
<param-name>environment</param-name>
<param-value>development</param-value>
</context-param>
<filter>
<filter-name>jersey-filter</filter-name>
<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>my.MainApplication</param-value>
</init-param>
</filter>
...
</web-app>
The problem is that I need to get context parameters from it. If there is another way, I would be grateful if somebody gave a hint.
I understand that Context annotation might not be purposed for this. Actually, I do not need ServletContext itself. If only I could get context params from web.xml, I would be absolutely happy.
Here is an example of what I really need:
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class MainApplication extends Application {
#Context ServletContext ctx;
#Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<Object>();
final String environment = ctx.getInitParameter("environment");
//final String environment = ... get context parameter from web xml
set.add(new AbstractBinder() {
#Override
protected void configure() {
bind(new BaseDataAccess(environment)).to(DataAccess.class);
}
});
//...
return set;
}
}
Thanks.
Since Jersey 2.5, ServletContext can be injected directly in constructor:
https://java.net/jira/browse/JERSEY-2184
public class MyApplication extends ResourceConfig {
public MyApplication(#Context ServletContext servletContext) {
// TODO
}
}
#Context can be made available on ResoureConfig by injecting it as a constructor parameter using #Context. Another way to access it is through an event handler.
Try the below code.
#ApplicationPath("...")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(StartupHandler.class);
}
private static class StartupHandler extends AbstractContainerLifecycleListener {
#Context
ServletContext ctx;
#Override
public void onStartup(Container container) {
// You can put code here for initialization.
}
}
// ...
Injection happens when you enter service method. Check if this is a problem.
There is interesting statement in documentation for Jersey version 1.18 for class
com.sun.jersey.spi.container.servlet.ServletContainer
The servlet or filter may be configured to have an initialization
parameter "com.sun.jersey.config.property.resourceConfigClass" or
"javax.ws.rs.Application" and whose value is a fully qualified name of
a class that implements ResourceConfig or Application. If the concrete
class has a constructor that takes a single parameter of the type Map
then the class is instantiated with that constructor and an instance
of Map that contains all the initialization parameters is passed as
the parameter.
If my understanding is correct the following constructor must be invoced with "an instance of Map that contains all the initialization parameters"
public class ExampleApplication extends Application {
public ExampleApplication(Map initParams) {
}
...
}
Here is appropriate part of web.xml:
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>experiment.service.ExampleApplication</param-value>
</init-param>
</servlet>
But somehow it failed for me with the following message:
SEVERE: Missing dependency for constructor public
experiment.service.ExampleApplication(java.util.Map) at parameter
index 0
And for current version of Jersey (2.5.1) there are no such statement in documentstion:
https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/servlet/ServletContainer.html
You can use the ApplicationEventListener interface to get the ServletContext. After initialization has finished, you can 'catch' an ApplicationEvent and use the injected ServletContext to work with.
Works fine with: org.glassfish.jersey : 2.12
For additional versions, pls use comments - i dont know, sry.
Jersey Docs - 20.1.2. Event Listeners
Your MainApplication:
#ApplicationPath("/")
public class MainApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> set = new HashSet<Class<?>>();
set.add(MainApplicationListener.class);
return classes;
}
}
... or alternative MainResourceConfig (I prefer to use this one):
public class MainResourceConfig extends ResourceConfig {
public MainResourceConfig() {
register(MainApplicationListener.class);
}
}
And the ApplicationEventListener:
public class MainApplicationListener implements ApplicationEventListener {
#Context
private ServletContext ctx; //not null anymore :)
#Override
public void onEvent(ApplicationEvent event) {
switch (event.getType()) {
case INITIALIZATION_FINISHED:
// do whatever you want with your ServletContext ctx
break;
}
#Override
public RequestEventListener onRequest(RequestEvent requestEvent) {
return null;
}
}
Don't use #Context in your Application but in a Resource class.
#Path("/foos")
public class FooResource {
#Context
ServletContext ctx;
#GET
public Response getFoos() {
return Response.ok().build();
}
}

Share variables between JAX-RS requests

I have what I think is a very basic question about JAX-RS but I somehow can't easily find the answer.
I am trying to refactor a REST service which uses a "standard" Javax servlet -- routing requests to methods by hand -- into an "cleaner" JAX-RS implementation. The current application sets some variables during the servlet init(). It assigns those as attributes of the HttpServlet class so they are available during each doGet() and can be passed as parameters to request processing methods. For clarity, one of these is a ConcurentHashMap that acts as a cache.
Now, with JAX-RS, I can extend Application to set my resource classes. I can also use the #Context annotation in each resource implementation to inject things like ServletContext when processing a request. But I do not know how to similarly inject variables set during application initialization.
I am using the Apache Wink 1.3.0 implementation of JAX-RS.
You can use a listener for init the cache and set to the context as attribute before the web application start. something like the following:
package org.paulvargas.shared;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class CacheListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
Map<String, String> dummyCache = new HashMap<String, String>();
dummyCache.put("greeting", "Hello Word!");
ServletContext context = sce.getServletContext();
context.setAttribute("dummyCache", dummyCache);
}
public void contextDestroyed(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.removeAttribute("dummyCache");
}
}
This listener is configured in the web.xml.
<listener>
<listener-class>org.paulvargas.shared.CacheListener</listener-class>
</listener>
<servlet>
<servlet-name>restSdkService</servlet-name>
<servlet-class>
org.apache.wink.server.internal.servlet.RestServlet
</servlet-class>
<init-param>
<param-name>applicationConfigLocation</param-name>
<param-value>/WEB-INF/application</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>restSdkService</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
You can use the #Context annotation for inject the ServletContext and retrieving the attribute.
package org.apache.wink.example.helloworld;
import java.util.*;
import javax.servlet.ServletContext;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import org.apache.wink.common.model.synd.*;
#Path("/world")
public class HelloWorld {
#Context
private ServletContext context;
public static final String ID = "helloworld:1";
#GET
#Produces(MediaType.APPLICATION_ATOM_XML)
public SyndEntry getGreeting() {
Map<String, String> dummyCache =
(Map<String, String>) context.getAttribute("dummyCache");
String text = dummyCache.get("greeting");
SyndEntry synd = new SyndEntry(new SyndText(text), ID, new Date());
return synd;
}
}

Categories