Redirect to URL in Spring MVC (REST API) with params - spring-boot

I have an endpoint in RestController. When request would be processed , there must performed redirecting to another URL and There need to pass one parameters in redirect URL.
#RestController
#RequiredArgsConstructor
#Slf4j
public class RestControllerImpl {
#Value("${uri-root}")
private String uriRoot;
private final Service service;
#PostMapping("/api")
public RedirectView performRequest(#RequestBody Transaction dto) {
service.perform(dto);
String referenceToken = "sldflh#2lhf*shdjkla"
String urlRedirect = uriRoot + "?token=" + referenceToken;
return new RedirectView(urlRedirect);
}
}
The code above doesn't work for me.
I was looking for information on stackoverflow, but it suggests either using ModelAndView or RedirectAttributes attributes. But I need endpoint to accept a Post request that would bring data that will be processed in the service layer.
It is not possible to complete this task. Could someone explain how this can work and how such a task can be accomplished ?

It worked.
#RestController
#RequiredArgsConstructor
#Slf4j
public class RestControllerImpl {
#Value("${uri-root}")
private String uriRoot;
private final Service service;
#PostMapping("/api")
public ResponseEntity createClient(#RequestBody Transaction dto) throws URISyntaxException {
service.perform(dto);
String referenceToken = "sldflh#2lhf*shdjkla"
String urlRedirect = uriRoot + "?token=" + referenceToken;
return ResponseEntity.created(new URI(urlRedirect)).build();
}
}

Related

Not able to call SOAP API in WebServiceGatewaySupport by Spring WebServiceTemplate - Need help to fix this issue

I am trying to call SOAP API in Java Spring Boot using WebServiceGatewaySupport by Spring WebServiceTemplate
Config java class
public WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller, ClientInterceptor clientInterceptor) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
//SOAP URL
webServiceTemplate.setDefaultUri("http://host/Services.asmx");
//Auth ---It seems issue is here only????? need to check
webServiceTemplate.setMessageSender(new Authentication());
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.afterPropertiesSet();
webServiceTemplate.setCheckConnectionForFault(true);
webServiceTemplate.setInterceptors((ClientInterceptor[]) Arrays.asList(createLoggingInterceptor()).toArray());
return webServiceTemplate;
}
SOAP Client Call
public class TicketClient extends WebServiceGatewaySupport {
public String getTicket(Ticket req) {
System.out.println("test inside webservice support1");
response = (AcquireTicketResponse) getWebServiceTemplate().marshalSendAndReceive(req);
Authentication Class
public class Authentication extends HttpUrlConnectionMessageSender {
#Override protected void prepareConnection(HttpURLConnection connection) {
String userpassword = username+":"+password+":"+domain;
String encoded =
Base64.getEncoder().withoutPadding().encodeToString(userpassword.getBytes(StandardCharsets.UTF_8));
connection.setRequestProperty("Authorization", "Basic "+encoded); connection.setRequestProperty("Content-Type", "application/xml"); super.prepareConnection(connection);
}
Not using Authetication class and add the above into
ClientInterceptor
public class SoapLoggingInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String username="test";
String password="test";
String domain = "#test";
String userpassword = username+":"+password+domain;
String encoded = Base64.getEncoder().withoutPadding().encodeToString(userpassword.getBytes(StandardCharsets.UTF_8));
messageContext.setProperty("Authorization", "Basic "+encoded);
messageContext.setProperty("Content-type", "XML");
Case -1 --->When I passed (user, pwd, domain and content-type) through messagesender, content type is taking but throwed "BAD REQUEST ERROR 400"....When i comment contenttype property, then it throwed "INTERNAL SERVER ERROR 500".
Case-2...when I passed (user, pwd, domain and content-type) through ClientInterceptor , always it throwed "INTERNAL SERVER ERROR 500"......It seems Authentication properties for the service are not going to API call.............................Please suggest some options
Both the cases, Authentication is not passing to service, if i comment,Authentication code (userid/pwd/domain) in both cases also...no efforts in output
After setting the user ID/pwd
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String username="test";
String password="test";
String domain = "#test";
String userpassword = username+":"+password+domain;
byte[] userpassword = (username+":"+password).getBytes(StandardCharsets.UTF_8);
String encoded = Base64.getEncoder().encodeToString(userpassword);
ByteArrayTransportOutputStream os = new
ByteArrayTransportOutputStream();
try {
TransportContext context = TransportContextHolder.getTransportContext();
WebServiceConnection conn = context.getConnection();
((HeadersAwareSenderWebServiceConnection) conn).addRequestHeader("Authorization", "Basic " + encoded);
} catch (IOException e) {
throw new WebServiceIOException(e.getMessage(), e);
}
First of all don't set the content type Spring WebServices will do that for you, messing around with that will only make things worse.
You should get the WebServiceConnection and cast that to a HeadersAwareSenderWebServiceConnection to add a header.
public class BasicAuthenticationInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String username="test#test";
String password="test";
byte[] userpassword = (username+":"+password).getBytes(UTF_8);
String encoded = Base64.getEncoder().encodeToString(userpassword);
WebServiceConnection conn = TransportContext.getConnection();
((HeadersAwareSenderWebServiceConnection) conn).addHeader("Authorization", "Basic " + encoded);
}
}
You also need to configure it. Assuming it is a bean don't call afterPropertiesSet (and ofcourse you are now using the ClientInterceptor remove the new Authentication() for your customized message sender.
The List<ClientInterceptor> will automatically create a list with all the interceptors so you can easily inject them.
#Bean
public WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller, List<ClientInterceptor> clientInterceptors) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate(marshaller);
//SOAP URL
webServiceTemplate.setDefaultUri("http://host/Services.asmx");
webServiceTemplate.setCheckConnectionForFault(true);
webServiceTemplate.setInterceptors(clientInterceptors);
return webServiceTemplate;
}
If this doesn't work there is something else you are doing wrong and you will need to get in touch with the server developers and get more information on the error.
Update:
Apparently you also need to provide a SOAP Action in your request, which you currently don't. For this you can specify the SoapActionCallback in the marshalSendAndReceive method. Which action to specify you can find in the WSDL you are using.
SoapActionCallback soapAction = new SoapActionCallback("SoapActionToUse");
response = (AcquireTicketResponse) getWebServiceTemplate().marshalSendAndReceive(req, soapAction);

Junit testing- java.lang.AssertionError: Status Expected:<200> but was:<403>

I know this kind of questions has been asked before and I tried many of the answers given in there and none is doing good, So can someone please help me out in this for getting proper solution
Its my Controller class
#RestController
#RequestMapping("/admin")
public class ABController {
#PutMapping("/train/{trainId}/{status}")
public ResponseEntity updateTrainstatus(#PathVariable String trainId, #PathVariable String status) {
return feignClient.updateTrainstatus(trainId, status);
}
}
Its my test controller class
private final String UPDATE_TRAIN_URI = "/admin/train?trainId=train100&status=approved";
#Test
public void testJsonController() throws Exception {
String trainId = "train100";
String status = "approved";
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.put(UPDATE_TRAIN_URI)
.contentType(MediaType.APPLICATION_JSON_VALUE).accept(MediaType.APPLICATION_JSON).characterEncoding("UTF-8");
this.mockMvc.perform(builder).andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Train status"))
.andDo(MockMvcResultHandlers.print());
}
Probably your project has a configured spring security. Than the controller requires some authentication - for instance a jwt token in a request header. In test your request does not have a valid value than You receive a 403 forbidden status

Handling Form Validation Result in ErrorHandler

I use spring-boot as a backend server. It has tens of Action Methods. As usual Some of them contains validation. Actually I use BindingResult and returns validation error for returning Http 400 Status.
#CrossOrigin
#RestController
public class ValidationTestController {
#RequestMapping(value = {"/validation-test", "/validation-test/"}, method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> login(#RequestBody #Valid final TestData data, final BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<>("Sorry incoming data is not valid!", HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("OK!", HttpStatus.OK);
}
private static final class TestData {
#NotNull
private String value;
}
}
My aim is removing follpwing lines:
if (result.hasErrors()) {
return new ResponseEntity<>("Sorry incoming data is not valid!", HttpStatus.BAD_REQUEST);
}
IMHO it's a cross cutting concern like Authentication and Auditing. I want to handle it in a one global ErrorHandler Method. It's possible to throw a CustomValidationException Before executing the method. So I can handle the exception in ErrorController.
Yes, you can centralize the exception handling logic at one place, using #ExceptionHandler which is a ControllerAdvice from Spring.
You can look at here

Spring MVC #RestController and redirect

I have a REST endpoint implemented with Spring MVC #RestController. Sometime, depends on input parameters in my controller I need to send http redirect on client.
Is it possible with Spring MVC #RestController and if so, could you please show an example ?
Add an HttpServletResponse parameter to your Handler Method then call response.sendRedirect("some-url");
Something like:
#RestController
public class FooController {
#RequestMapping("/foo")
void handleFoo(HttpServletResponse response) throws IOException {
response.sendRedirect("some-url");
}
}
To avoid any direct dependency on HttpServletRequest or HttpServletResponse I suggest a "pure Spring" implementation returning a ResponseEntity like this:
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(newUrl));
return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
If your method always returns a redirect, use ResponseEntity<Void>, otherwise whatever is returned normally as generic type.
Came across this question and was surprised that no-one mentioned RedirectView. I have just tested it, and you can solve this in a clean 100% spring way with:
#RestController
public class FooController {
#RequestMapping("/foo")
public RedirectView handleFoo() {
return new RedirectView("some-url");
}
}
redirect means http code 302, which means Found in springMVC.
Here is an util method, which could be placed in some kind of BaseController:
protected ResponseEntity found(HttpServletResponse response, String url) throws IOException { // 302, found, redirect,
response.sendRedirect(url);
return null;
}
But sometimes might want to return http code 301 instead, which means moved permanently.
In that case, here is the util method:
protected ResponseEntity movedPermanently(HttpServletResponse response, String url) { // 301, moved permanently,
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, url).build();
}
As the redirections are usually needed in a not-straightforward path, I think throwing an exception and handling it later is my favourite solution.
Using a ControllerAdvice
#ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
NotLoggedInException.class
})
protected ResponseEntity<Object> handleNotLoggedIn(
final NotLoggedInException ex, final WebRequest request
) {
final String bodyOfResponse = ex.getMessage();
final HttpHeaders headers = new HttpHeaders();
headers.add("Location", ex.getRedirectUri());
return handleExceptionInternal(
ex, bodyOfResponse,
headers, HttpStatus.FOUND, request
);
}
}
The exception class in my case:
#Getter
public class NotLoggedInException extends RuntimeException {
private static final long serialVersionUID = -4900004519786666447L;
String redirectUri;
public NotLoggedInException(final String message, final String uri) {
super(message);
redirectUri = uri;
}
}
And I trigger it like this:
if (null == remoteUser)
throw new NotLoggedInException("please log in", LOGIN_URL);
if you #RestController returns an String you can use something like this
return "redirect:/other/controller/";
and this kind of redirect is only for GET request, if you want to use other type of request use HttpServletResponse

When use ResponseEntity<T> and #RestController for Spring RESTful applications

I am working with Spring Framework 4.0.7, together with MVC and Rest
I can work in peace with:
#Controller
ResponseEntity<T>
For example:
#Controller
#RequestMapping("/person")
#Profile("responseentity")
public class PersonRestResponseEntityController {
With the method (just to create)
#RequestMapping(value="/", method=RequestMethod.POST)
public ResponseEntity<Void> createPerson(#RequestBody Person person, UriComponentsBuilder ucb){
logger.info("PersonRestResponseEntityController - createPerson");
if(person==null)
logger.error("person is null!!!");
else
logger.info("{}", person.toString());
personMapRepository.savePerson(person);
HttpHeaders headers = new HttpHeaders();
headers.add("1", "uno");
//http://localhost:8080/spring-utility/person/1
headers.setLocation(ucb.path("/person/{id}").buildAndExpand(person.getId()).toUri());
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
to return something
#RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Person> getPerson(#PathVariable Integer id){
logger.info("PersonRestResponseEntityController - getPerson - id: {}", id);
Person person = personMapRepository.findPerson(id);
return new ResponseEntity<>(person, HttpStatus.FOUND);
}
Works fine
I can do the same with:
#RestController (I know it is the same than #Controller + #ResponseBody)
#ResponseStatus
For example:
#RestController
#RequestMapping("/person")
#Profile("restcontroller")
public class PersonRestController {
With the method (just to create)
#RequestMapping(value="/", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void createPerson(#RequestBody Person person, HttpServletRequest request, HttpServletResponse response){
logger.info("PersonRestController - createPerson");
if(person==null)
logger.error("person is null!!!");
else
logger.info("{}", person.toString());
personMapRepository.savePerson(person);
response.setHeader("1", "uno");
//http://localhost:8080/spring-utility/person/1
response.setHeader("Location", request.getRequestURL().append(person.getId()).toString());
}
to return something
#RequestMapping(value="/{id}", method=RequestMethod.GET)
#ResponseStatus(HttpStatus.FOUND)
public Person getPerson(#PathVariable Integer id){
logger.info("PersonRestController - getPerson - id: {}", id);
Person person = personMapRepository.findPerson(id);
return person;
}
My questions are:
when for a solid reason or specific scenario one option must be used mandatorily over the other
If (1) does not matter, what approach is suggested and why.
ResponseEntity is meant to represent the entire HTTP response. You can control anything that goes into it: status code, headers, and body.
#ResponseBody is a marker for the HTTP response body and #ResponseStatus declares the status code of the HTTP response.
#ResponseStatus isn't very flexible. It marks the entire method so you have to be sure that your handler method will always behave the same way. And you still can't set the headers. You'd need the HttpServletResponse.
Basically, ResponseEntity lets you do more.
To complete the answer from Sotorios Delimanolis.
It's true that ResponseEntity gives you more flexibility but in most cases you won't need it and you'll end up with these ResponseEntity everywhere in your controller thus making it difficult to read and understand.
If you want to handle special cases like errors (Not Found, Conflict, etc.), you can add a HandlerExceptionResolver to your Spring configuration. So in your code, you just throw a specific exception (NotFoundException for instance) and decide what to do in your Handler (setting the HTTP status to 404), making the Controller code more clear.
According to official documentation: Creating REST Controllers with the #RestController annotation
#RestController is a stereotype annotation that combines #ResponseBody
and #Controller. More than that, it gives more meaning to your
Controller and also may carry additional semantics in future releases
of the framework.
It seems that it's best to use #RestController for clarity, but you can also combine it with ResponseEntity for flexibility when needed (According to official tutorial and the code here and my question to confirm that).
For example:
#RestController
public class MyController {
#GetMapping(path = "/test")
#ResponseStatus(HttpStatus.OK)
public User test() {
User user = new User();
user.setName("Name 1");
return user;
}
}
is the same as:
#RestController
public class MyController {
#GetMapping(path = "/test")
public ResponseEntity<User> test() {
User user = new User();
user.setName("Name 1");
HttpHeaders responseHeaders = new HttpHeaders();
// ...
return new ResponseEntity<>(user, responseHeaders, HttpStatus.OK);
}
}
This way, you can define ResponseEntity only when needed.
Update
You can use this:
return ResponseEntity.ok().headers(responseHeaders).body(user);
A proper REST API should have below components in response
Status Code
Response Body
Location to the resource which was altered(for example, if a resource was created, client would be interested to know the url of that location)
The main purpose of ResponseEntity was to provide the option 3, rest options could be achieved without ResponseEntity.
So if you want to provide the location of resource then using ResponseEntity would be better else it can be avoided.
Consider an example where a API is modified to provide all the options mentioned
// Step 1 - Without any options provided
#RequestMapping(value="/{id}", method=RequestMethod.GET)
public #ResponseBody Spittle spittleById(#PathVariable long id) {
return spittleRepository.findOne(id);
}
// Step 2- We need to handle exception scenarios, as step 1 only caters happy path.
#ExceptionHandler(SpittleNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public Error spittleNotFound(SpittleNotFoundException e) {
long spittleId = e.getSpittleId();
return new Error(4, "Spittle [" + spittleId + "] not found");
}
// Step 3 - Now we will alter the service method, **if you want to provide location**
#RequestMapping(
method=RequestMethod.POST
consumes="application/json")
public ResponseEntity<Spittle> saveSpittle(
#RequestBody Spittle spittle,
UriComponentsBuilder ucb) {
Spittle spittle = spittleRepository.save(spittle);
HttpHeaders headers = new HttpHeaders();
URI locationUri =
ucb.path("/spittles/")
.path(String.valueOf(spittle.getId()))
.build()
.toUri();
headers.setLocation(locationUri);
ResponseEntity<Spittle> responseEntity =
new ResponseEntity<Spittle>(
spittle, headers, HttpStatus.CREATED)
return responseEntity;
}
// Step4 - If you are not interested to provide the url location, you can omit ResponseEntity and go with
#RequestMapping(
method=RequestMethod.POST
consumes="application/json")
#ResponseStatus(HttpStatus.CREATED)
public Spittle saveSpittle(#RequestBody Spittle spittle) {
return spittleRepository.save(spittle);
}

Resources