Rest template giving null body and status 302 - spring

I am trying to consume a rest call in my mvc controller, however every time I do it returns a null body with http status as 302.Also I am using spring boot with spring security to get https.
I've followed code samples from here: http://websystique.com/springmvc/spring-mvc-4-restful-web-services-crud-example-resttemplate/
and Get list of JSON objects with Spring RestTemplate however none of these work
Can someone please point me in the right direction
Thank you,
REST
#RequestMapping(value = "/api/*")
#RestController
public class PostApiController {
static final Logger logger = LogManager.getLogger(PostApiController.class.getName());
private final PostService postService;
#Inject
public PostApiController(final PostService postService) {
this.postService = postService;
}
//-------------------Retrieve All Posts--------------------------------------------------------
#RequestMapping(value = "post", method = RequestMethod.GET)
public ResponseEntity<List<Post>> getAllPosts() {
List<Post> posts = postService.findAllPosts();
if(posts.isEmpty()){
return new ResponseEntity<List<Post>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<Post>>(posts, HttpStatus.OK);
}
}
Controller
#Controller
public class PostController {
static final Logger logger = LogManager.getLogger(PostController.class.getName());
public static final String REST_SERVICE_URI = "http://localhost:8080/api"; //"http://localhost:8080/api";
private final PostService postService;
#Inject
public PostController(final PostService postService) {
this.postService = postService;
}
#SuppressWarnings("unchecked")
#RequestMapping(value = "/getAll")
// public String create(#Valid Post post, BindingResult bindingResult, Model
// model) {
public ModelAndView getAll() {
// if (bindingResult.hasErrors()) {
// return "mvchome";
// }
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Post>> responseEntity = restTemplate.exchange(REST_SERVICE_URI+"/post",HttpMethod.GET, null, new ParameterizedTypeReference<List<Post>>() {});
// ResponseEntity<Post[]> responseEntity = restTemplate.getForEntity(REST_SERVICE_URI+"/post", Post[].class);
List<Post> postsMap = responseEntity.getBody();
MediaType contentType = responseEntity.getHeaders().getContentType();
HttpStatus statusCode = responseEntity.getStatusCode();
// List<LinkedHashMap<String, Object>> postsMap = restTemplate.getForObject(REST_SERVICE_URI+"/post", List.class);
// String s= REST_SERVICE_URI+"/post";
// logger.info(s);
if(postsMap!=null){
for(Post map : postsMap){
logger.info("User : id="+map.getUid());
}
}else{
logger.info("No user exist----------");
}
//List<Post> postList = postService.findAllPosts();
ModelAndView mav = new ModelAndView("mvchome");
mav.addObject("postsList", postsMap);
Post newpost = new Post();
mav.addObject("post", newpost);
return mav;
}
}
***** to fix my issue I modified my code to just do a redirect on select url paths instead of "/*"
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat =
new TomcatEmbeddedServletContainerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
//used to be just collection.addPattern("/*"); now I changed it to specify which path I want it to redirect
collection.addPattern("/mvchome/*");
collection.addPattern("/home/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(createHttpConnector());
return tomcat;
}

The http status 302 is usually caused by wrong url setting.
First, make sure that public ResponseEntity<List<Post>> getAllPosts() {} method is called (just print List<Post> result inside it).
If it's called properly and you can get the return value inside public ModelAndView getAll() {}.
The problem should be the directing setting of the public ModelAndView getAll() {} method.
Check if you make something wrong in your web.xml or spring configuration. Pay attention to the configuration which redirects to views and the url mapping of your dispatcher servlet.
If public ResponseEntity<List<Post>> getAllPosts() {} is called but you can't get the return value, then it should be the issues of directing setting of the public ResponseEntity<List<Post>> getAllPosts() {} method.
Check your spring configuration and web.xml for that. The possible cause usually will be the misuse of wildcard in the configuration and web.xml, or just unnoticed wrong mapping.

Related

How to rewrite URLs with Spring (Boot) via REST Controllers?

Let's say I have the following controller with its parent class:
#RestController
public class BusinessController extends RootController {
#GetMapping(value = "users", produces = {"application/json"})
#ResponseBody
public String users() {
return "{ \"users\": [] }"
}
#GetMapping(value = "companies", produces = {"application/json"})
#ResponseBody
public String companies() {
return "{ \"companies\": [] }"
}
}
#RestController
#RequestMapping(path = "api")
public class RootController {
}
Data is retrieved by calling such URL's:
http://app.company.com/api/users
http://app.company.com/api/companies
Now let's say I want to rename the /api path to /rest but keep it "available" by returning a 301 HTTP status code alongside the new URI's
e.g. client request:
GET /api/users HTTP/1.1
Host: app.company.com
server request:
HTTP/1.1 301 Moved Permanently
Location: http://app.company.com/rest/users
So I plan to change from "api" to "rest" in my parent controller:
#RestController
#RequestMapping(path = "rest")
public class RootController {
}
then introduce a "legacy" controller:
#RestController
#RequestMapping(path = "api")
public class LegacyRootController {
}
but now how to make it "rewrite" the "legacy" URI's?
That's what I'm struggling with, I can't find anything Spring-related on the matter, whether on StackOverflow or elsewhere.
Also I have many controllers AND many methods-endpoints so I can not do this manually (i.e. by editing every #RequestMapping/#GetMapping annotations).
And project I'm working on is based on Spring Boot 2.1
Edit: I removed the /business path because actually inheritance doesn't work "by default" (see questions & answers like Spring MVC #RequestMapping Inheritance or Modifying #RequestMappings on startup ) - sorry for that.
I finally found a way to implement this, both as a javax.servlet.Filter AND a org.springframework.web.server.WebFilter implementation.
In fact, I introduced the Adapter pattern in order to transform both:
org.springframework.http.server.ServletServerHttpResponse (non-reactive) and
org.springframework.http.server.reactive.ServerHttpResponse (reactive)
because on the contrary of the Spring's HTTP requests' wrappers which share org.springframework.http.HttpRequest (letting me access both URI and HttpHeaders), the responses's wrappers do not share a common interface that does it, so I had to emulate one (here purposely named in a similar fashion, HttpResponse).
#Component
public class RestRedirectWebFilter implements Filter, WebFilter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
ServletServerHttpRequest request = new ServletServerHttpRequest((HttpServletRequest) servletRequest);
ServletServerHttpResponse response = new ServletServerHttpResponse((HttpServletResponse) servletResponse);
if (actualFilter(request, adapt(response))) {
chain.doFilter(servletRequest, servletResponse);
}
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (actualFilter(exchange.getRequest(), adapt(exchange.getResponse()))) {
return chain.filter(exchange);
} else {
return Mono.empty();
}
}
/**
* Actual filtering.
*
* #param request
* #param response
* #return boolean flag specifying if filter chaining should continue.
*/
private boolean actualFilter(HttpRequest request, HttpResponse response) {
URI uri = request.getURI();
String path = uri.getPath();
if (path.startsWith("/api/")) {
String newPath = path.replaceFirst("/api/", "/rest/");
URI location = UriComponentsBuilder.fromUri(uri).replacePath(newPath).build().toUri();
response.getHeaders().setLocation(location);
response.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
response.flush();
return false;
}
return true;
}
interface HttpResponse extends HttpMessage {
void setStatusCode(HttpStatus status);
void flush();
}
private HttpResponse adapt(ServletServerHttpResponse response) {
return new HttpResponse() {
public HttpHeaders getHeaders() {
return response.getHeaders();
}
public void setStatusCode(HttpStatus status) {
response.setStatusCode(status);
}
public void flush() {
response.close();
}
};
}
private HttpResponse adapt(org.springframework.http.server.reactive.ServerHttpResponse response) {
return new HttpResponse() {
public HttpHeaders getHeaders() {
return response.getHeaders();
}
public void setStatusCode(HttpStatus status) {
response.setStatusCode(status);
}
public void flush() {
response.setComplete();
}
};
}
}
Since it looks like you want to preserve the 301 but also have it return a response, you do have the option to wire in your RootController into your LegacyRootController
That way you can provide reuse the logic you have in the RootController but return different response codes and serve different paths on your LegacyRootController
#RestController
#RequestMapping(path = "api")
public class LegacyRootController {
private final RootController rootController;
public LegacyRootController(RootController rootController) {
this.rootController = rootController;
}
#GetMapping(value = "users", produces = {"application/json"})
#ResponseStatus(HttpStatus.MOVED_PERMANENTLY) // Respond 301
#ResponseBody
public String users() {
return rootController.users(); // Use rootController to provide appropriate response.
}
#GetMapping(value = "companies", produces = {"application/json"})
#ResponseStatus(HttpStatus.MOVED_PERMANENTLY)
#ResponseBody
public String companies() {
return rootController.companies();
}
}
This will allow you to serve /api/users to serve up a response with a 301, while also allowing you to serve /rest/users with your standard response.
If you would like to add the Location headers, you can have your LegacyRootController return a ResponseEntity to provide the body, code and header values.
#GetMapping(value = "users", produces = {"application/json"})
public ResponseEntity<String> users() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation("...");
return new ResponseEntity<String>(rootController.users(), responseHeaders, HttpStatus.MOVED_PERMANENTLY);
}
If you want to serve multiple endpoints that does not serve different status codes, you can simply provide multiple paths
#RequestMapping(path = {"api", "rest"})

Get return value in custom annotation spring aop

I have write a simple custom annotation to set HttpHeaders to ResponseEntity because of duplicating the code every where .
Annotation Interface and Class.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface JsonHeader {}
#Component
#Aspect
public class JsonHeaderAspect {
private final Log logger = LogFactory.getLog(getClass());
#Around(value = "#annotation(JsonHeader)")
public Object aroundServiceResponse(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
return proceedingJoinPoint.proceed(new Object[] {headers});
}
}
RestController Class
#RestController
#RequestMapping(path = "/login")
public class LoginRestController {
private final Log logger = LogFactory.getLog(getClass());
#Autowired
LoginServiceImpl loginService;
#JsonHeader
#RequestMapping(value = "/user",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResponseBean> postCheckUser(#RequestBody LoginBean loginBean) {
ResponseBean responseBean = loginService.checkUser(loginBean);
return new ResponseEntity<ResponseBean>(responseBean,headers, HttpStatus.OK);
}
}
Now I want to get the return HttpHeaders value annotaion to rest controller class.
Is any one can describe why it happen and how to fix this issue it will be great helpful. Thanks in advance
You can do this simply by modifying your advice like this. You don't need to do anything in the controller.
#Around(value = "#annotation(requestMapping)")
public Object aroundServiceResponse(ProceedingJoinPoint proceedingJoinPoint,RequestMapping requestMapping) throws Throwable {
String[] consumes = requestMapping.consumes();
consumes[consumes.length] = MediaType.APPLICATION_JSON_VALUE;
String[] produces = requestMapping.produces();
produces[produces.length] = MediaType.APPLICATION_JSON_VALUE;
return proceedingJoinPoint.proceed();
}

spring-data-rest: Validator not being invoked

I am using springboot 2.0.1.RELEASE with spring-data-rest and followed the workaround mentioned here and my Validator is still not being invoked. Here are the details:
ValidatorRegistrar: Workaround for a bug
#Configuration
public class ValidatorRegistrar implements InitializingBean {
private static final List<String> EVENTS;
static {
List<String> events = new ArrayList<String>();
events.add("beforeCreate");
events.add("afterCreate");
events.add("beforeSave");
events.add("afterSave");
events.add("beforeLinkSave");
events.add("afterLinkSave");
events.add("beforeDelete");
events.add("afterDelete");
EVENTS = Collections.unmodifiableList(events);
}
#Autowired
ListableBeanFactory beanFactory;
#Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
#Override
public void afterPropertiesSet() throws Exception {
Map<String, Validator> validators = beanFactory.getBeansOfType(Validator.class);
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
EVENTS.stream().filter(p -> entry.getKey().startsWith(p)).findFirst()
.ifPresent(p -> validatingRepositoryEventListener.addValidator(p, entry.getValue()));
}
}
}
Validator class:
#Component("beforeSaveBidValidator")
public class BeforeSaveBidValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Bid.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Bid bid = (Bid)target;
if (!bid.getAddendaAcknowledged()) {
errors.rejectValue("addendaAcknowledged",
"addendaAcknowledged is not true");
}
}
}
Custom RestController for Bids:
#RestController
#RequestMapping(path = "/bids")
public class BidController {
private BidRepository bidRepository;
#Autowired
public BidController(
BidRepository bidRepository) {
this.bidRepository = bidRepository;
}
#PutMapping("{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
Rest Client Test Code:
Bid bid = new Bid()
...
bid.setAddendaAcknowledged(false)
Map<String, String> uriVariables = new HashMap<String, String>()
uriVariables.put("id", bid.id)
HttpHeaders headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
HttpEntity<Bid> entity = new HttpEntity<>(bid, headers)
ResponseEntity<String> response = restTemplate.exchange(
"/bids/{id}", HttpMethod.PUT, entity, Bid.class, bid.id)
// Expected: response.statusCode == HttpStatus.BAD_REQUEST
// Found: response.statusCode == HttpStatus.OK
// Debugger showed that Validator was never invoked.
Any idea what I am missing?
You are trying to use your validator with custom controller, not SDR controller. In this case you can just add it to your controller with #InitBinder annotation:
#RestController
#RequestMapping("/bids")
public class BidController {
//...
#InitBinder("bid") // add this parameter to apply this binder only to request parameters with this name
protected void bidValidator(WebDataBinder binder) {
binder.addValidators(new BidValidator());
}
#PutMapping("/{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
#Component annotation on your validator is not necessary as well as ValidatorRegistrar class.
How to use validators with SDR controllers you can read in my another answer.

In the Spring3,How to call a another server's controller in my controller

I have 3 servers,serverA,serverB,serverC,Now in the serverC,some request from serverB is by processed,and then,I don't know what is the result(response),if it's resultA,I want give the resultA to the serverA as a request,else give the serverB.
so what I can do something in the serverC's controller,or there is something wrong in the desgin.
Please tell me what I should to do,Thanks.
This is my code.
serverA
#RestController
public class ControllerA {
#RequestMapping(value = "/methodA", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<String> methodA(#RequestBody String something) {
// some process
return null;
}
serverB
#RestController
public class ControllerB {
#RequestMapping(value = "/methodB", consumes =MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> methodB(#RequestBody String something) {
// some process
return null;
}
serverC
#RestController
public class ControllerC {
public ResponseEntity<String> methodC(#RequestBody String someReq) {
if (checkPam(someReq)) {
**// I want to call the ControllerA in serverA.**
}else {
**// I want to call the ControllerB in serverB.**
}
return null;
}
You can simply Use RestTemplate:
#RestController
public class ControllerC {
public ResponseEntity<String> methodC(#RequestBody String someReq) {
RestTemplate restTemplate = new RestTemplate();
if (checkPam(someReq)) {
String fooResourceUrl
= "http://path-to-server-a/path-to-service-a";
ResponseEntity<String> response
= restTemplate.getForEntity(fooResourceUrl , String.class);
}else {
String fooResourceUrl
= "http://path-to-server-b/path-to-service-b";
ResponseEntity<String> response
= restTemplate.getForEntity(fooResourceUrl , String.class);
}
return null;
}
As you can see, I instantiate RestTemplate object by new operator, you can also declare RestTemplate bean in your context and then autowire it in your controller class.

Trying to pass objects to controller(Spring MVC)

I am trying to test my controller. Spring populates my Profile object but it is empty. I can set the email before the call bu it still is null. How to jag pass a Profile in a proper way?
private MockHttpServletRequest request;
private MockHttpServletResponse response;
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
request.setContentType("application/json");
this.response = new MockHttpServletResponse();
}
#Test
public void testPost() {
request.setMethod("POST");
request.setRequestURI("/user/"); // replace test with any value
final ModelAndView mav;
Object handler;
try {
Profile p = ProfileUtil.getProfile();
p.setEmail("test#mail.com");
request.setAttribute("profile", p);
System.out.println("before calling the email is " + p.getEmail());
handler = handlerMapping.getHandler(request).getHandler();
mav = handlerAdapter.handle(request, response, handler);
Assert.assertEquals(200, response.getStatus());
// Assert other conditions.
} catch (Exception e) {
}
}
This is the controller
#RequestMapping(value = "/", method = RequestMethod.POST)
public View postUser(ModelMap data, #Valid Profile profile, BindingResult bindingResult) {
System.out.println("The email is " + profile.getEmail());
}
Try using following signature for the controller function postUser.
public View postUser(ModelMap data, #ModelAttribute("profile") #Valid Profile profile, BindingResult bindingResult)
Hope this helps you. Cheers.

Resources