Spring HATEOAS: How to advertise resource services? - spring

I've got Spring HATEOAS working for accessing a specific resource, such as
http://localhost:8080/user/1
But I want to also be able to advertise a service url:
http://localhost:8080/user
For instance, if you do a GET / , I want to return the service resources I advertise. Right now the only one is /auth.
#RequestMapping(value = "/", method = RequestMethod.GET)
#ResponseBody
public HttpEntity<AuthenticationResource> post() {
AuthenticationResource resource = new AuthenticationResource();
resource.add(linkTo(methodOn(AuthenticationController.class).authenticate()).withSelfRel());
return new ResponseEntity<AuthenticationResource>(resource, HttpStatus.OK);
}
#RequestMapping(value = "/auth", method = RequestMethod.POST, consumes = "application/json")
#ResponseBody
public void authenticate() {
//users.save(user);
}
Currently this is not compiling because linkTo doesn't take a void argument, which I presume is the return type of my authenticate method
What I WANT is this:
{"links":[{"rel":"someString","href":"http://localhost/auth"}]}
How do I accomplish this while staying within HATEOAS best practice?

This.
#RequestMapping(value = "/", method = RequestMethod.GET)
#ResponseBody
public HttpEntity<ResourceSupport> post() {
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(methodOn(AuthenticationController.class).authenticate()).withRel("authenticate"));
return new ResponseEntity<ResourceSupport>(resource, HttpStatus.OK);
}
#RequestMapping(value = "/auth", method = RequestMethod.POST, consumes = "application/json")
#ResponseBody
public HttpEntity<AuthenticationResource> authenticate() {
AuthenticationResourceAssembler assembler = new AuthenticationResourceAssembler();
AuthenticationResource resource = assembler.toResource(new Authentication());
return new ResponseEntity<AuthenticationResource>(resource, HttpStatus.OK);
}

Related

Springboot evaluate api path from Controller method name

#RestController
#RequestMapping(value = "/api/orders/")
public class OrderController {
#PostMapping("create")
public ResponseEntity<OrderResponseV2> create(
#RequestBody OrderRequestV2 orderRequest) {
OrderResponse response = createOrderService.createOrder(orderRequest);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
}
Is there a way to get the whole API path including root context during runtime using Relfection from class+method name?

Feign Client Error Handling

I am using Feign Client,
I have a Location service. So I created a client for my LocationService using FeignClient.
#FeignClient(url="http://localhost:9003/location/v1", name="location-service")
public interface LocationControllerVOneClient {
#RequestMapping(value = "/getMultipleLocalities", method = RequestMethod.POST)
public Response<Map<Integer, Locality>> getMultipleLocalities(List<Integer> localityIds);
#RequestMapping(value = "/getMultipleCities", method = RequestMethod.POST)
public Response<Map<Integer, City>> getMultipleCities(List<Integer> cityIds);
#RequestMapping(value = "/getMultipleStates", method = RequestMethod.POST)
public Response<Map<Integer, State>> getMultipleStates(List<Integer> stateIds);
#RequestMapping(value = "/getMultipleCitiesName", method = RequestMethod.POST)
public Response<Map<Integer, String>> getMultipleCitiesName(MultiValueMap<String, String> formParams);
#RequestMapping(value = "/getMultipleStatesName", method = RequestMethod.POST)
public Response<Map<Integer, String>> getMultipleStatesName(MultiValueMap<String, String> formParams);
#RequestMapping(value = "/getMultipleLocalitiesName", method = RequestMethod.POST)
public Response<Map<Integer, String>> getMultipleLocalitiesName(MultiValueMap<String, String> formParams);
}
Now other services might call this LocationService via LocationClient.
I want to do exception handling for this Feign Client(LocationClient) at a common place(i.e. I just donot want each caller to do this. This should be part of LocationClient). Exception Could be connection refused(if LocationService is down), timeout etc.
You could use a feign ErrorDecoder for exception handling. Below is the url for your reference.
https://github.com/OpenFeign/feign/wiki/Custom-error-handling
Example :
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyBadRequestException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
To get this ErrorDecoder you need create a bean for it as below :
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
You can define a fallback client that is called when an exception like timeout or connection refused comes up:
#FeignClient(url="http://localhost:9003/location/v1", name="location-service", fallback=LocationFallbackClient.class)
public interface LocationControllerVOneClient {
...
}
LocationFallbackClient must implement LocationControllerVOneClient.

How to redirect to URL using SPRING BeanNameViewResolver

Is it possible to redirect to another URL inside Spring MVC controller while using BeanNameViewResolver?
Perhaps in a way just escaping the bean name resolving?
This obviously only works with the UrlBasedViewResolver
#RequestMapping(value = "/**", method = RequestMethod.GET)
public ModelAndView postPage(HttpServletRequest req){
return "redirect:http://localhost:9090/flowID";
}
Found the Answer
#RequestMapping(value = "/**", method = RequestMethod.GET)
public View postPage(HttpServletRequest req, Map<String, Object> model){
RedirectView redirectView = new RedirectView("http://localhost:9090/flowID");
return redirectView;
}

spring boot setContentType is not working

I'm trying to return an image on spring-boot (1.2.2)
How should I set the content-type?
Non of the following are working for me (meaning that response headers are not containing 'content-type' header at all ):
#RequestMapping(value = "/files2/{file_name:.+}", method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> getFile2(final HttpServletResponse response) throws IOException {
InputStream is = //someInputStream...
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.setContentType("image/jpeg");
InputStreamResource inputStreamR = new InputStreamResource(is);
return new ResponseEntity<>(inputStreamR, HttpStatus.OK);
}
#RequestMapping(value = "/files3/{file_name:.+}", method = RequestMethod.GET)
public HttpEntity<byte[]> getFile3() throws IOException {
InputStream is = //someInputStream...
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
return new HttpEntity<>(IOUtils.toByteArray(is), headers);
}
Firstly, you'll need to apply the #ResponseBody annotation in addition to #RequestMapping, unless you are using #RestController at the class level instead of just #Controller. Also, try the produces element of #RequestMapping e.g.
#RequestMapping(value = "/files2/{file_name:.+}", method = RequestMethod.GET, produces = {MediaType.IMAGE_JPEG_VALUE})
This should 'narrow the primary mapping' and ensure the correct content type is set. See the docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-requestmapping-produces
Got it... Had to add ByteArrayHttpMessageConverter to WebConfiguration class:
#Configuration
#EnableWebMvc
#ComponentScan
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
httpMessageConverters.add(new ByteArrayHttpMessageConverter());
}
}
And the then my second attempt (getFile3()) was working correctly

Spring Framework TEST RESTful Web Service (Controller) Offline i.e. No Server, No Database

I have a very simple RESTful Controller that consumes and produces JSON. I need to test this controller offline i.e. no server running, no database running. And I am going nuts for not being able to find a solution. My intial test cases will include:
Test REST URIs i.e. GET, POST, PUT, DELETE - I must be able to Assert data returned against data sent.
Assert will test JSON data
I have the following URIs:
/pcusers - Returns all users
/pcusers/{id} - Return a specific user
/pcusers/create/{pcuser} - Add user to db
/pcusers/update/{pcuser} - Update user
/pcusers/delete/{id} - Delete User
NOTE: This is NOT a typical MVC application. I DO NOT have Views. I have a pure REST controller that spits out JSON and consumes data in JSON format.
If someone could guide me in the right direction would be really appreciated.
Just to be clear how my code looks like:
#Controller
#RequestMapping("/pcusers")
public class PcUserController {
protected static Logger logger = Logger.getLogger(PcUserController.class);
#Resource(name = "pcUserService")
private PcUserService pcUserService;
#RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public List<PcUser> readAll() {
logger.debug("Delegating to service to return all PcUsers");
return pcUserService.readAll();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET, consumes = "application/json", produces = "application/json")
#ResponseBody
public PcUser read(#PathVariable String id) {
logger.debug("Delegating to service to return PcUser " + id);
return pcUserService.read(id);
}
#RequestMapping(value = "/create/{pcUser}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
#ResponseBody
public boolean create(#PathVariable PcUser pcUser) {
logger.debug("Delegating to service to create new PcUser");
return pcUserService.create(pcUser);
}
#RequestMapping(value = "/update/{pcUser}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
#ResponseBody
public boolean update(#PathVariable PcUser pcUser) {
logger.debug("Delegating to service to update existing PcUser");
return pcUserService.update(pcUser);
}
#RequestMapping(value = "/delete/{id}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
#ResponseBody
public boolean delete(#PathVariable String id) {
logger.debug("Delegating to service to delete existing PcUser");
return pcUserService.delete(id);
}
}
UPDATE (2/5/2012):
After some research, I came across a Spring framework called spring-test-mvc. It looks very promising and I have managed to get a good start on this. But now I have a new problem. When I submit a GET request to "/pcusers/{id}", the control is passed to read method which is responsible for handling that mapping. Inside that method I have a pcUserService that does a read. Now, the problem is when I run this test, the pcUserService instance inside real controller is NULL; and therefore it ends up crashing as read cannot be called on a NULL object.
Here's PcUserControllerTest code:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:/applicationContextTest.xml")
public class PcUserControllerTest {
#Autowired
PcUserService pcUserService;
#Autowired
PcUserController pcUserController;
PcUser pcUser;
#Before
public void setUp() throws Exception {
pcUser = new PcUser("John", "Li", "Weasley", "john", "john", new DateTime());
pcUserService.create(pcUser);
}
public void tearDown() throws Exception {
pcUserService.delete(pcUser.getId());
}
#Test
public void shouldGetPcUser() throws Exception {
standaloneSetup(pcUserController)
.build()
.perform(get("/pcusers/" + pcUser.getId()).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Here is one suggestion that should give you some ideas. I assume that you are familiar with the SpringJUnit4ClassRunner and the #ContextConfiguration. Start by creating an test application context that contains PcUserController and a mocked PcUserService. In the example PcUserControllerTest class below, Jackson is used to convert JSON messages and Mockito is used for mocking.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(/* Insert test application context here */)
public class PcUserControllerTest {
MockHttpServletRequest requestMock;
MockHttpServletResponse responseMock;
AnnotationMethodHandlerAdapter handlerAdapter;
ObjectMapper mapper;
PcUser pcUser;
#Autowired
PcUserController pcUserController;
#Autowired
PcUserService pcUserServiceMock;
#Before
public void setUp() {
requestMock = new MockHttpServletRequest();
requestMock.setContentType(MediaType.APPLICATION_JSON_VALUE);
requestMock.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
responseMock = new MockHttpServletResponse();
handlerAdapter = new AnnotationMethodHandlerAdapter();
HttpMessageConverter[] messageConverters = {new MappingJacksonHttpMessageConverter()};
handlerAdapter.setMessageConverters(messageConverters);
mapper = new ObjectMapper();
pcUser = new PcUser(...);
reset(pcUserServiceMock);
}
}
Now, we have all the code needed to create the tests:
#Test
public void shouldGetUser() throws Exception {
requestMock.setMethod("GET");
requestMock.setRequestURI("/pcusers/1");
when(pcUserServiceMock.read(1)).thenReturn(pcUser);
handlerAdapter.handle(requestMock, responseMock, pcUserController);
assertThat(responseMock.getStatus(), is(HttpStatus.SC_OK));
PcUser actualPcUser = mapper.readValue(responseMock.getContentAsString(), PcUser.class);
assertThat(actualPcUser, is(pcUser));
}
#Test
public void shouldCreateUser() throws Exception {
requestMock.setMethod("POST");
requestMock.setRequestURI("/pcusers/create/1");
String jsonPcUser = mapper.writeValueAsString(pcUser);
requestMock.setContent(jsonPcUser.getBytes());
handlerAdapter.handle(requestMock, responseMock, pcUserController);
verify(pcUserServiceMock).create(pcUser);
}

Resources