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.
Related
I have a recipe app with several routes (get, getById, post, delete, etc).
So far it hasn't been difficult to implement them, since, for example, a get route would be like this:
Controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Recipe> getAllRecipe() {
return recipesRepository.findAll();
}
Or by id:
Repository:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
Recipe findBy_id(String _id);
}
Controller:
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Recipe getRecipeById(#PathVariable("id") String id) {
return recipesRepository.findBy_id(id);
}
I want to make a search screen in my app, so when you write something (like "straw") I get all results that contain the word "straw" in my database (like strawberry ice cream, strawberry milkshake...) in the name field (my model has a field called name).
My knowledge in Java isnt by any means good, so I'm lost here, so far I have a route that gets all results using "name", but I need it to search anything that contains that value, not only if you put the exact value (like if I put "Stew", it gives me "Stew" result if it exists, but I need to get every result that have the word "Stew" in their name). If it helps, this is the code for that:
Repository:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
Recipe findBy_id(String _id);
Recipe findByName(String name);
}
Controller:
#RequestMapping(value = "/recipe/{name}", method = RequestMethod.GET)
public Recipe getRecipeByName(#PathVariable("name") String name) {
return recipesRepository.findByName(name);
}
About the case sensitive problem, you can add keywords, like this:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
{
public List<Recipe> findByNameLikeIgnoreCase(String name);
}
I think you can use smth like this one :
public interface RecipesRepository extends MongoRepository<Recipe, String> {
{
public List<Recipe> findByNameLike(String name);
}
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.
This is the following code of my RESTful service class:
#RequestScoped
#Path("/empresas")
public class EmpresaEndpoint {
#Inject
private EmpresaRB empresaRB;
#GET
#Path("/{id:[0-9][0-9]*}")
#Produces("application/json")
public Response findById(#PathParam("id") final Long id) {
//TODO: retrieve the empresas
Empresa empresas = null;
if (empresas == null) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok(empresas).build();
}
#GET
#Produces("application/json")
public List<Empresa> listAll(
#QueryParam("start") final Integer startPosition,
#QueryParam("max") final Integer maxResult) {
//TODO: retrieve the empresa
return empresaRB.getEmpresas();
}
}
If I wanted to access all the data stored on "Empresa" via jQuery, I would do:
$.getJSON( "rest/empresas", function( data ) {
//whatever is needed.
}
The code above would access the "listAll" method. So how can I access the "findById" method and pass the necessary parameter?
Assuming you have a variable called empresaId that holds the id for the entity, this should work.
$.getJSON( "rest/empresas/" + empresaId, function(data) {
// Whatever is required here
}
Well without having used that particular framework, it looks like it's mapping to the right method based on the path - it will use findById if the path has an ID, e.g.
$.getJSON("rest/empresas/100", function(data) {
// ...
}
(That will find the item with ID 100... obviously substitute the ID of the item you want to find. We don't know where that's coming from, but "rest/empresas/" + id may well be all you need.)
In my initial code, there was no query being connected to the variable "empresa", on the method findById().
I created a query on the repository class and assigned it to the variable. Problem Solved.
Thank you all for the time lended.
I have a web service that looks like:
#Path("/ws")
public class Ws {
#GET public Record getOne(#QueryParam("id") Integer id) { return record(id); }
#GET public List<Record> getAll() { return allRecords(); }
}
The idea is that I can either call:
http://ws:8080/ws?id=1 to get a specific record
http://ws:8080/ws to get all available records
However when I use the second URL, the first #GET method is called with a null id.
Is there a way to achieve what I want without using different paths?
I think this can be achieved with Spring using the #RequestMapping(params={"id"}) and #RequestMapping annotations for the first and second methods respectively but I can't use Spring in that project.
Since the path is the same, you cannot map it to a different method. If you change the path using REST style mapping
#Path("/ws")
public class Ws {
#GET #Path("/{id}") public Response getOne(#PathParam("id") Integer id) { return Response.status(200).entity(record(id)).build(); }
#GET public Response getAll() { return Response.status(200).entity(allRecords()).build(); }
then you should use:
http://ws:8080/ws/1 to get a specific record
http://ws:8080/ws to get all available records
I'm trying to add custom methods to my Spring Data repository PersonRepository as described in 1.3 Custom implementations for Spring Data repositories and exposing these method through REST. The initial code is from Accessing JPA Data with REST sample, here is the code for added/modified classes:
interface PersonRepositoryCustom {
List<Person> findByFistName(String name);
}
class PersonRepositoryImpl implements PersonRepositoryCustom, InitializingBean {
#Override
public void afterPropertiesSet() throws Exception {
// initialization here
}
#Override
public List<Person> findByFistName(String name) {
// find the list of persons with the given firstname
}
}
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(#Param("name") String name);
}
When I run the application and visit http://localhost:8080/portfolio/search/, I get the following response body:
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
Why findByFirstName is not exposed even if it is available in the PersonRepository interface?
Also, is there a way to dynamically/programmatically add respositories to be exposed via REST?
After two days, I have solved in this way.
Custom Repository Interface:
public interface PersonRepositoryCustom {
Page<Person> customFind(String param1, String param2, Pageable pageable);
}
Custom Repository Implementation
public class PersonRepositoryImpl implements PersonRepositoryCustom{
#Override
public Page<Person> customFind(String param1, String param2, Pageable pageable) {
// custom query by mongo template, entity manager...
}
}
Spring Data Repository:
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String>, PersonRepositoryCustom {
Page<Person> findByName(#Param("name") String name, Pageable pageable);
}
Bean Resource representation
public class PersonResource extends org.springframework.hateoas.Resource<Person>{
public PersonResource(Person content, Iterable<Link> links) {
super(content, links);
}
}
Resource Assembler
#Component
public class PersonResourceAssembler extends ResourceAssemblerSupport<Person, PersonResource> {
#Autowired
RepositoryEntityLinks repositoryEntityLinks;
public PersonResourceAssembler() {
super(PersonCustomSearchController.class, PersonResource.class);
}
#Override
public PersonResource toResource(Person person) {
Link personLink = repositoryEntityLinks.linkToSingleResource(Person.class, person.getId());
Link selfLink = new Link(personLink.getHref(), Link.REL_SELF);
return new PersonResource(person, Arrays.asList(selfLink, personLink));
}
}
Custom Spring MVC Controller
#BasePathAwareController
#RequestMapping("person/search")
public class PersonCustomSearchController implements ResourceProcessor<RepositorySearchesResource> {
#Autowired
PersonRepository personRepository;
#Autowired
PersonResourceAssembler personResourceAssembler;
#Autowired
private PagedResourcesAssembler<Person> pagedResourcesAssembler;
#RequestMapping(value="customFind", method=RequestMethod.GET)
public ResponseEntity<PagedResources> customFind(#RequestParam String param1, #RequestParam String param2, #PageableDefault Pageable pageable) {
Page personPage = personRepository.customFind(param1, param2, pageable);
PagedResources adminPagedResources = pagedResourcesAssembler.toResource(personPage, personResourceAssembler);
if (personPage.getContent()==null || personPage.getContent().isEmpty()){
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(Person.class);
List<EmbeddedWrapper> embedded = Collections.singletonList(wrapper);
adminPagedResources = new PagedResources(embedded, adminPagedResources.getMetadata(), adminPagedResources.getLinks());
}
return new ResponseEntity<PagedResources>(adminPagedResources, HttpStatus.OK);
}
#Override
public RepositorySearchesResource process(RepositorySearchesResource repositorySearchesResource) {
final String search = repositorySearchesResource.getId().getHref();
final Link customLink = new Link(search + "/customFind{?param1,param2,page,size,sort}").withRel("customFind");
repositorySearchesResource.add(customLink);
return repositorySearchesResource;
}
}
The reason these methods are not exposed is that you're basically free to implement whatever you want in custom repository methods and thus it's impossible to reason about the correct HTTP method to support for that particular resource.
In your case it might be fine to use a plain GET, in other cases it might have to be a POST as the execution of the method has side effects.
The current solution for this is to craft a custom controller to invoke the repository method.
For GET methods I have used the following approach:
create a dummy #Query method in the Repository (LogRepository.java)
create a custom interface with the same method declared (LogRepositoryCustom.java)
create an implementation of the custom interface (LogRepositoryImpl.java)
Using this approach I don't have to manage projections and resource assembling.
#RepositoryRestResource(collectionResourceRel = "log", path = "log")
public interface LogRepository extends PagingAndSortingRepository<Log, Long>,
LogRepositoryCustom {
//NOTE: This query is just a dummy query
#Query("select l from Log l where l.id=-1")
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
public interface LogRepositoryCustom {
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
In the implementation you are free to use the repository methods or going directly to the persistence layer:
public class LogRepositoryImpl implements LogRepositoryCustom{
#Autowired
EntityManager entityManager;
#Autowired
LogRepository logRepository;
#Override
public Page<Log> findAllFilter(
#Param("options") String options,
#Param( "eid") Long[] entityIds,
#Param( "class" ) String cls,
Pageable pageable) {
//Transform kendoui json options to java object
DataSourceRequest dataSourceRequest=null;
try {
dataSourceRequest = new ObjectMapper().readValue(options, DataSourceRequest.class);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
Session s = entityManager.unwrap(Session.class);
Junction junction = null;
if (entityIds != null || cls != null) {
junction = Restrictions.conjunction();
if (entityIds != null && entityIds.length > 0) {
junction.add(Restrictions.in("entityId", entityIds));
}
if (cls != null) {
junction.add(Restrictions.eq("cls", cls));
}
}
return dataSourceRequest.toDataSourceResult(s, Log.class, junction);
}
The answer is that you haven't followed instructions. Your PersonRepository has to extend both PagingAndSortingRepository<Person, Long> AND PersonRepositoryCustomin order to achieve what you're after. See https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations
Another option we used as well is to implement a custom repository factory for your specific storage type.
You can extend from RepositoryFactoryBeanSupport, build your own PersistentEntityInformation and take care of CRUD ops in a default repo impl for your custom data storage type. See JpaRepositoryFactoryBean for example. You maybe need to implement about 10 classes in total but then it gets reusable.
Try using
class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
...
}
The implementing class name should be PersonRepositoryCustomImpl instead of PersonRepositoryImpl.