Error Contoller Howto : Spring-boot + Spring-Data-Rest - java

Spring Boot with Spring Data Rest - how to use a custom error handler.
Created an error controller I tried to skip the default error handler by using following code.
Why it is not working!
#Configuration
#EnableJpaRepositories
#Import(RepositoryRestMvcConfiguration.class)
#EnableAutoConfiguration(exclude = { BasicErrorController.class })
#EnableMetrics
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
.....................
.....................
and error controller as below
#Component
#RestController
#RequestMapping(value = "/error")
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
// TODO Auto-generated constructor stub
}
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public String error() {
return "Error handling";
}
#Override
public String getErrorPath() {
return PATH;
}
}

I haven't used this kind of solution, but, it seems that your request mapping is not right.
The request mapping of CustomErrorController is '/error', and in
#RequestMapping(value = PATH)
public String error() {
return "Error handling";
}
There is a another '/error' in request mapping path. Then the url for this error handler is '/error/error'.

You have #RequestMapping("/error") annotation on your controller and second #RequestMapping("/error") on your method. This results in /error/error mapping, not the /error mapping as you specified in getErrorPath() method and maybe in your configuration (application.properties, server.path.error).

Related

Springboot Dynamically modify the ResourceHandlerRegistry mapping

I've a spring boot web application that can serve files from a static file location in server.
I've specified the location in properties file and using it to configure the ResourceHandlerRegistry.
#SpringBootApplication
public class MyWebApplication {
#Value("${targetdirectory}")
private String targetDirectory;
#Bean
WebMvcConfigurer configurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
targetDirectory = StringUtils.appendIfMissing(targetDirectory, "/", "/");
targetDirectory = StringUtils.prependIfMissing(targetDirectory, "file:/", "file:/");
registry.addResourceHandler("/resourcetarget/**").addResourceLocations(targetDirectory);
}
};
}
public static void main(String[] args) {
SpringApplication.run(MyWebApplication.class, args);
}
}
Everything works as expected. Now I have to dynamically set the resource location based on user input.
After the application is loaded, the user triggers an HTTP post request where he can specify the directory by which can be used as the resource location.
So after that any requests to the /resourcetarget/** should be mapped to the directory which the user specified. Following is the controller I have.
#RestController
#RequestMapping(value = "api/locations", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class MyController {
#PostMapping
public ResponseEntity<Object> handleLocationSet(#RequestBody LocationDTO locationDto) {
String newFileLocation = locationDto.getLocation();
// How do I update the ResourceHandlerRegistry mapping for /resourcetarget/**
// with the new location received here?
return ResponseEntity.ok();
}
}
How can I update the mapping for this dynamic location for a static resource url. Please help

Spring Boot - Mock a POST REST request to an external API

I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");

Getting 406 Could not find acceptable representation /Spring JSON Test. How to ignore .htm extension in tests?

Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html

#RequestMapping java.lang.AssertionError: Status Expected :200 Actual :404

Assertion error using #RequestMapping annotation outside of the class
I am getting this error message:
java.lang.AssertionError: Status
Expected :200
Actual :404
My Controller is like this
#Service
#RestController
#RequestMapping("/execute/files")
#ResponseBody
public class ControllerFiles {
#Autowired
#Qualifier("fileRunner")
ProcessRunnerInterface processRunnerInterfaceFiles;
public InputState executeRestFile(#RequestParam String name) throws ExecutionFailedException, URISyntaxException {
///code///
}
public List<String>....{
///code///
}
}
My Test
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ControllerFilesTest {
#Autowired
private MockMvc mockMvc;
#Autowired
ControllerFiles controllerFiles;
#Test
public void testSpringMvcGetFiles() throws Exception {
this.mockMvc.perform(get("/execute/files").param("name", "Spring Community Files"))
.andDo(print()).andExpect(status().isOk());
}
}
But when I have my code like this the test work fine!
#Service
#RestController
public class ControllerFiles {
#Autowired
#Qualifier("fileRunner")
ProcessRunnerInterface processRunnerInterfaceFiles;
#RequestMapping("/execute/files")
#ResponseBody
public InputState executeRestFile(#RequestParam String name) throws ExecutionFailedException, URISyntaxException {
///code///
}
public List<String>....{
///code///
}
}
Any ideas what is going wrong?
The methods in your RestController need to be marked as #RequestMapping if you want them to be picked up as request resources. If you want to keep the base request mapping at the controller level as in your first RestController then you need to do the following:
#RestController
#RequestMapping("my/path")
public class MyController {
#RequestMapping("/")
public InputState myMethod() {
...
}
}
As it is said in documentation:
In the above example, #RequestMapping is used in a number of places. The first usage is on the type (class) level, which indicates that all handler methods in this controller are relative to the /appointments path.
So the class level #RequestMapping is only indicating relativnes. It is not declare actual resource paths based on public methods only. So you need to annotate your method like this:
#GetMapping
public InputState executeRestFile(#RequestParam String name) throws Exception {
// omited
}
Or like this:
#RequestMapping(method = RequestMethod.GET)
public InputState executeRestFile(#RequestParam String name) throws Exception {
// omited
}

SpringApplication not able to instantiate bean

I am new to Spring & WebService and trying a few guides on Spring.io.
I planned to create a basic RESTful WebService which consumes Google Direction API and returns just the status.
Here are the classes:
Resource
#JsonIgnoreProperties(ignoreUnknown=true)
public class Direction {
// getters & setters
public Direction() {
super();
}
private String status;
public String toString() {
return status;
}
}
Controller
#Controller
public class Consumer {
public Consumer() {
super();
}
#Resource
private String url;
#Resource
private RestTemplate client;
#Resource
private String apiKey;
#RequestMapping(value = "/directions", method=RequestMethod.GET)
public #ResponseBody Direction consume(#RequestParam(value="source") String source, #RequestParam(value="destination") String destination) {
return consumeDirections(buildURI(source, destination));
}
// Builds URI
private String buildURI(...) {
...
}
private Direction consumeDirections(final String requestURI) {
return client.getForObject(requestURI, Direction.class);
}
}
Configuration v1
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
Springconfig
http://pastebin.com/dsNVBWQq
Spring returns that No qualifying bean of type [java.lang.String] found for dependency.
This happens for all the beans in Consumer.
However, this works Configuration v2
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
#Resource
private Consumer consumer;
public void execute() {
System.out.println(consumer.consume("x", "z"));
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("application-config.xml");
context.getBean(Application.class).execute();
}
}
Some observations
#Resouce(Explicitly define bean) doesnt work for v1
SpringApplication is not aware of the Springconfig and fails during bean instantiation
I would like to understand why this issue crops up and how to resolve it?
The reason is very easy, the xml config is not loaded. have a look at Spring-Boot: XML Config
if you don't wanna touch existing xml, you need another #configuration annotated class and #ImportResource to load the xml configuration, just like the document says.
IMO, you don't need apiKey and url in the config, you should annotate them with #value, and define them in a .properties file. There are also default settings of spring boot, you get take advantage of it. like, name the properities application.properities and put it on classpath, spring boot will load it automatically.

Categories

Resources