I need to fetch data from API. Right now I have controller and service:
#RestController
public class SearcherController {
private SearcherService searcherService;
public SearcherController(SearcherService searcherService) {
this.searcherService = searcherService;
}
#GetMapping("/{name}")
public ResponseEntity<Output> getUrl(#RequestParam(value = "name", required = false) String name) {
if (name == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new Output("BAD REQUEST"));
} else {
return ResponseEntity.status(HttpStatus.OK).body(searcherService.getOutput(name));
}
}
}
In the controller, I check if my request param is empty. If so I return bad request.
If it is not null I go to the service. And in service, I do the call to API.
#Service
public class SearcherService {
public Output getName(String name) {
List<Result> results = getDataFromApi(name);
// call to Api and other calculation
}
}
And fetching data from API could give me a list with results or could give me an empty list.
And that is my question.
What is the best practice? Should I move this code to controller call the API in the controller and there make results !=null and do something about it? I'm thinking about best practice in this case...
Related
I have a springboot project with 2 controller files as below:
File1.java
#PostMapping("/test")
public String testMap(String s){
if(s!=null){
return "found it";
}
else {
// need to go to POST request in another controller
}
return "not found";
}
File2.java
#PostMapping("/test2")
public String testMap2(String s){
if(s!=null){
return "found it";
}
return "not found 2";
}
I have tried adding java HttpURLConnection lines to send a POST request in File1.java but it does not perform the operations within testMap2, instead it exits with not found
Could you please give some suggestions on how I could accomplish this?
You could use RestTemplate to create another POST request, although I strongly suggest avoiding that.
Since both of these controllers are in the same project, try extracting the common logic into a #Service which should be injected in both controllers.
For example:
File1.java
#RestController
public class MyFirstController {
private MyBusinessLogic myBusinessLogic;
// Constructor injection
public MyFirstController(MyBusinessLogic myBusinessLogic) {
this.myBusinessLogic = myBusinessLogic;
}
#PostMapping("/test")
public String testMap(String s){
if(s!=null){
return "found it";
}
else {
return myBusinessLogic.doSomething(s);
}
return "not found";
}
}
File2.java:
#RestController
public class MySecondController {
private MyBusinessLogic myBusinessLogic;
// Constructor injection
public MySecondController(MyBusinessLogic myBusinessLogic) {
this.myBusinessLogic = myBusinessLogic;
}
#PostMapping("/test2")
public String testMap2(String s){
if(s!=null){
return myBusinessLogic.doSomething(s);
}
return "not found 2";
}
}
Finally create a service for the common logic:
#Service
public class MyBusinessLogic {
public String doSomething(String s) {
// common logic goes here
}
}
You can use RestTemplate.
Lets say our controller looks like this:
#RestController
#RequestMapping(value = "first/")
public class FirstRestController {
#PostMapping("test")
public String getTest(String s){
return service.doSomething(s);
}
}
Basically, add this method as a bean in one of your config classes. #Bean puts the method in application context. Now we can inject this method in our services.
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Now, one of our service methods in Second App, we must call the endpoint of First.
#Service
public class SecondAppService{
#Autowired
private RestTemplate restTemplate;
public String callFirst() {
final URI uri =UriComponentsBuilder.fromUriString(PATH+"first/").toUri();
restTemplate.postForEntity(uri, "something", String.class);
// check your resttemplate docs, i used postForEntity here.
// if necessery return something with response, this method expects the return string but you get the idea.
}
}
This should work.
I have an object, and of its attributes is a List. I want to send this object from Postman to my service. I'm using Spring 5.2.7 (Spring MVC, not SpringBoot) and Hibernate 5.4.17 and Java 8. My problem is very similar to this one: I want to send a Postman POST request with an Array: members: ["william", "eric", "ryan"]
This is the class I'm trying to pass in Postman (POST method):
public class ChatDescriptionDto {
private String chatID;
private List<String> members;
private String chatType;
public String getChatID() {
return chatID;
}
public void setChatID(String chatID) {
this.chatID = chatID;
}
public List<String> getMembers() {
return members;
}
public void setMembers(List<String> members) {
this.members = members;
}
public void addMembers(List<String> members)
{
if(this.members == null)
this.members = new ArrayList<>();
this.members.addAll(members);
}
public void addMember(String member)
{
if(this.members == null)
this.members = new ArrayList<>();
this.members.add(member);
}
public String getChatType() {
return chatType;
}
public void setChatType(String chatType) {
this.chatType = chatType;
}
}
I've tried this and it didn't work:
{
"chatID": "123",
"members": ["P2001222833","P2001640916"],
"chatType": "personal"
}
Edit: This is my controller:
#PostMapping("/initiateChat")
public String initiateChat(#RequestBody ChatDescriptionDto chat)
{
return chatServiceLocal.initiateChat(chat)?"Chat Description created":"Failure! Could not save.";
}
Edit 2: The method which I've written in the question, "members": ["P2001222833","P2001640916"], is the correct one. Turns out, there was some error in the server so it never started and I didn't check that.
Having no information about the Controller class you're using, the first thing I'd assume is that you're receiving an empty object, which means that Spring simply skipped the serialization. This is the case when you don't specify the parameter of the method as #RequestBody. First, make sure that you do have the annotation.
#RestController
#RequestMapping("/")
public class TestController {
#RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity test(#RequestBody ChatDescriptionDto dto) {
System.out.println(dto);
return ResponseEntity.ok().build();
}
}
If that's not the case, I'd assume that the problem is with the content type you're using. Spring uses JSON by default, but you can change it in your endpoint's configuration.
To send a simple object request, you do:
{
"member":"kola"
}
To send a list object request, you do:
{
"member": ["kola","wale","ojo"]
}
This is more like listing array elements.
Any error that pops up after this, is basically not because of the request you sent.
I'm creating a Java application using Elastic Search.
Here is the link for my project.
https://github.com/chanakaDe/ensembl-elastic-rest
In this project, I have implemented a rest controller to take data as JSON.
This is the controller class. Now it only has 2 methods. But I need to add some method like this.
#RequestMapping(value = "/find-by/{id}/{param1}/{param2}/{param3}", method = RequestMethod.GET)
public Iterable<Track> findAllWithParams(#PathVariable int id, #PathVariable String param1, #PathVariable String param2, #PathVariable String param3) {
return trackService.someMethodWithParams(id, param1, param2, param3);
}
What I need to do is take some values from user and send them into Elastic server and make a search. I just refered some of these links and got some idea.
https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-search.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html
TrackService.java and TrackServiceImpl.java are implemented by TrackRepository.java and it's extended by ElasticsearchRepository default class. https://github.com/chanakaDe/ensembl-elastic-rest/blob/master/src/main/java/com/chanaka/book/repository/TrackRepository.java
I need to take values via REST URL and create an object like following and pass that to Elastic Server. How can I implement that with my current project configuration ?
{
"query": {
"constant_score" : {
"filter" : {
"terms" : { "user" : ["kimchy", "elasticsearch"]}
}
}
}
}
This is my TrackService.java interface.
public interface TrackService {
Track save(Track track);
Track findOne(int id);
Iterable<Track> findAll();
}
And also this is my TrackServiceImpl.java class implemented by TrackService.java.
public class TrackServiceImpl implements TrackService {
private TrackRepository trackRepository;
#Autowired
public void setTrackRepository(TrackRepository trackRepository) {this.trackRepository = trackRepository;}
#Override
public Track save(Track track) {
return trackRepository.save(track);
}
#Override
public Track findOne(int id) {
return trackRepository.findOne(id + "");
}
#Override
public Iterable<Track> findAll() {
return trackRepository.findAll();
}
}
Do I need to implement a custom method for that ? Or is there any default methods like findAll() and findOne() ?
Simply pass an object and get the value ?
I think, there's no such existing method and you need to create your own by using QueryBuilders.wrapperQuery(query.toString()) and ElasticsearchTemplate. Just to note, wrapperQuery supports only query not filter. But you can achieve filter context query with constant_score.
help me please.
I have the code of controller like this:
#RequestMapping(method = RequestMethod.GET)
public String showOrders(#RequestParam(value = "status", required = false) String status, Model model) {
if(status != null) {
Order.Status orderStatus = Order.Status.valueOf(status);
if (orderStatus != null) model.addAttribute("currentStatus", orderStatus);
}
return "admin/orders";
}
#ModelAttribute("currentStatus")
public Order.Status populateCurrentStatus() {
return Order.Status.PAYMENT;
}
#ModelAttribute("orders")
public List<Order> populateOrders(#ModelAttribute("currentStatus") Order.Status status) {
return orderBo.getByStatus(status);
}
I want the default currentStatus to be equal to Order.Status.PAYMENT, but if the controller receives a GET request with the argument status (on method showOrders), then replace currentStatus in the model to the status transmitted in the request.
And the populateOrders should return a different list of orders, in accordance with the new status. But unfortunately, this is not happening. Method populateOrders always gets currentStatus equal Order.Status.PAYMENT and it never changes.
You can add #RequestParam to your #ModelAttribute definition in order to populate #ModelAttribute differently, depending on a request parameter.
This should work:
#RequestMapping(method = RequestMethod.GET)
public String showOrders() {
//no need to manually modify currentStatus in the model anymore
return "admin/orders";
}
#ModelAttribute("currentStatus")
public Order.Status populateCurrentStatus(#RequestParam(value = "status", defaultValue = "PAYMENT") String status) {
return Order.Status.valueOf(status);
}
#ModelAttribute("orders")
public List<Order> populateOrders(#ModelAttribute("currentStatus") Order.Status status) {
return orderBo.getByStatus(status);
}
Personal note:
I personally dislike Spring's #ModelAttribute system for defining default model attributes, especially in case of more complex models or in more complex controllers. It becomes hard to maintain because it's hard to control what exactly goes into the model (for example, for some requests, maybe you don't want the whole model populated, e.g. you only need currentStatus but not the list of orders. I prefer populating the model manually (Model.addAttribute(...)) - it's not much extra work and everything is explicit and easy to debug if a problem pops out later on.
I'm trying to build a RESTful API using Spring MVC. I'm shooting for clean and manageable code where the package structure follows the url structure.
So here is what I've got:
// com.test.api.library
#RequestMapping("/library/{libraryId}")
public Library getLibrary(#PathVariable long libraryId) {
return service.getLibraryById(libraryId);
}
// com.test.api.library.book
#RequestMapping("/library/{libraryId}/book/{bookId}")
public Book getBook(#PathVariable long libraryId, #PathVariable long bookId) {
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
While this works, I find it messy and error-prone to have to repeat "/library/{libraryId}" in all inherited #RequestMappings, /library is likely to be to root of a big part of the API and it should be written once and reused instead of written everywhere.
I would like to rewrite the book-class to something like this:
// com.test.api.library.book
#RequestMapping("/book/{bookId}")
public Book getBook(#PathVariable long bookId) {
// long libraryId magically given to me from the library-class's getLibrary()
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
Is there any way Spring can help me here? It is acceptable for me to use normal java inheritance, spring annotation or anything else that helps me to not write "/library/{libraryId}" as a part of every url I ever write.
I believe this question has been asked & answered before: Spring MVC #RequestMapping Inheritance
That said, here is one way to reduce the amount of duplicate information. I don't actually do this in my own code because I think having the URI right next to the code is more maintainable, even if it means a little duplication.
#RequestMapping(URI_LIBRARY)
public interface LibraryNamespace {
public static String URI_LIBRARY = "/library/{libraryId}";
}
#RequestMapping(URI_BOOK)
public interface BookNamespace {
public static String URI_BOOK = LibraryNamespace.URI_LIBRARY + "/book/{bookId}";
}
#Controller
public class LibraryController implements LibraryNamespace {
#RequestMapping("")
public Library get(#PathVariable long libraryId) {
return service.getLibraryById(libraryId);
}
}
#Controller
public class BookController implements BookNamespace {
#RequestMapping("")
public Book get(#PathVariable long libraryId, #PathVariable long bookId) {
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
}
Since I wouldn't take this approach myself, I haven't actually tried this solution! Based on my understanding of Spring, I think it should work though...
Use a polymorphic parent approach.
#Controller
public class CommentsController {
#RequestMapping(value="/comments", method = RequestMethod.GET)
public #ResponseBody String index() {
/* kludge to allow optional path parameters */
return index(null, null);
}
#RequestMapping(value="/{parent_collection}/{parent_id}/comments", method = RequestMethod.GET)
public #ResponseBody String index(#PathVariable("parent_collection") String parentCollection, #PathVariable("parent_id") String parentId) {
if (parentCollection == null) {
return "all comments";
}
else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
/* get parent, then get comments for parent */
return "comments for single post";
}
else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
/* get parent, then get comments for parent */
return "comments for single customer";
}
else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
/* get parent, then get comments for parent */
return "comments for single movie";
}
}
#RequestMapping(value = "/comments/{id}", method = RequestMethod.GET)
public #ResponseBody String show(#PathVariable Integer id) {
/* kludge to allow optional path parameters */
return show(null, null, id);
}
#RequestMapping(value = "/{parent_collection}/{parent_id}/comments/{id}", method = RequestMethod.GET)
public #ResponseBody String show(#PathVariable("parent_collection") String parentCollection, #PathVariable("parent_id") String parentId, #PathVariable Integer id) {
/* get comment, then get parent from foreign key */
if (parentCollection == null) {
return "single comment";
}
else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
return "single comment for single post";
}
else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
return "single comment for single customer";
}
else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
return "single comment for single movie";
}
}
}
Additionally, you could use a base controller to route the URI prefix to parent resources (/libraries/{library_id}/../..), add the parent models to the request scope, and then let the regular request mappings handle the rest of the URI to child resources (/../../books/1). I don't have an example of this off-hand.
Side note. Singular nested resources are generally regarded as an antipattern for URI design. A controller should handle its own resources. The most common implementations make the key for the singular nested resource unique, i.e., not dependent on its parent resource. For instance, a database record primary key. However, there are situations where the key might not be unique, such as an ordinal or position value (e.g., book 1, chapter 1, chapter 2), or maybe even a natural key (e.g., book ISBN, person SSN, email address, username, filename).
Example of canonical URIs for nested resources:
/articles => ArticlesController#index
/articles/1 => ArticlesController#show
/articles/1/comments => CommentsController#index
/articles/1/comments/2 => CommentsController#show (okay, but not preferred)
/comments/2 => CommentsController#show (preferred)
I don't think it's possible. But you can have the #RequestMapping annotation on the class itself, so it will save you at least some typing.
#Controller
#RequestMapping("/library/{libraryId}")
public class HelloWorldController {
#RequestMapping(value="/book/{bookId}")
public ModelAndView helloWorld() {
....
}
}