Spring boot + elasticsearch prefix adding doen't work - spring

I have this entity:
#Data
#Document(indexName = "foo.user")
public class ElUser {
#Id
private String id;
#Field(type = FieldType.Text)
private String fullName;
}
And this Repository:
#Repository
interface ElasticsearchUserRepository extends ElasticsearchRepository<ElUser, String> {
List<ElUser> findAllByFullNameIsLike(String name);
}
And it works fine. But I want to use a prefix like this(.withPathPrefix("foo")):
#EnableElasticsearchRepositories
#Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
#Bean
#Override
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.withPathPrefix("foo")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
And remove it from entity:
#Document(indexName = "user")
But I get an exception:
org.springframework.data.elasticsearch.NoSuchIndexException: Index [foo] not found.; nested exception is [foo] ElasticsearchStatusException[Elasticsearch exception [type=index_not_found_exception, reason=no such index [foo]]]

The path prefix you can configure is not a prefix for the index name like foo in foo.user, it's for the path in the URL which might be needed for some routing/proxying/dispatching software between the application and Elasticsearch.
Let's imagine you have two Elasticsearch clusters at foo:9200 and bar:9200 and there is a nginx proxy in front of them at proxy:8080 which will route an request to proxy:8080/foo/abc to foo:9200/abc and proxy:8080/bar/abc to bar:9200/abc, then you would configure you client to connect to proxy:8080 with a pathPrefix of foo.
So it's not for the use case you have.
Edit:
I have an example of how to provide an index prefix in my blog post "How to provide a dynamic index name in Spring Data Elasticsearch using SpEL", in the section Use a value from the application configuration, that might fit your needs.
_

Related

Spring boot REST API best way to choose in client side which field to load

Hi I have implemented a mock solution to my problem and I'm pretty sure something better already exist.
Here's that I want to achieve :
I have created a point to load categories with or without subCategories
/api/categories/1?fields=subCategories
returns
{
"id":"1",
"name":"test",
"subCategories":[{
"id":"1",
"name":"test123"
}]
}
/api/categories/1
returns
{
"id":"1",
"name":"test"
}
My entities
#Entity
class Category{
#Id
private String id;
private String name;
private Set<SubCategory> subCategories;
}
#Entity
class SubCategory{
#Id
private String id;
private String name;
}
I have removed services since this is not the point.
I've created CategoryDTO and SubCategoryDTO classes with the same fields as Category and SubCategory
The converter
class CategoryDTOConverter{
CategoryDTO convert(Category category,String fields){
CategoryDTO dto=new CategoryDTO();
dto.setName(category.getName());
if(StringUtils.isNotBlank(fields) && fields.contains("subCategories"){
category.getSubCategories().forEach(s->{
dto.getSubcategories().add(SubCategoryDTOConverter.convert(s));
}
}
}
}
I used com.cosium.spring.data.jpa.entity.graph.repository to create an EntityGraph from a list of attribute path
#Repository
interface CategoryRepository extends EntityGraphJpaRepository<Category, String>{
Optional<T> findById(String id,EntityGraph entityGraph);
}
Controller
#RestController
#CrossOrigin
#RequestMapping("/categories")
public class CategoryController {
#GetMapping(value = "/{id}")
public ResponseEntity<CategoryDTO> get(#PathVariable("id") String id, #RequestParam(value="fields",required=false) String fields ) throws Exception {
Optional<Category> categOpt=repository.findById(id,fields!=null?EntityGraphUtils.fromAttributePaths(fields):null);
if(categOpt.isEmpty())
throws new NotFoundException();
return ResponseEntity.ok(categoryDTOConverter.convert(categOpt.get(),fields);
}
}
This is a simple example to illustrate what I need to do
I don't want to load fields that clients doesn't want to use
How could I do this in a better way ?
Take a look at GraphQL since it is a perfect match for your use case. With GraphQL it is the client that decides which attributes it wants to receive by providing in the POST request body exactly which attributes are needed to be included in the response. This is way more manageable than trying to handle all this on your own.
Spring Boot recently added its own Spring GraphQL library, so it is quite simple to integrate it in your Spring Boot app.

How to make Set using spring-data-aerospike

Environment:
spring-boot v2.0.4 RELEASE
spring-data-aerospike v2.0.1.RELEASE
java - 8
Here are my application code and properties.
// application.properties
aerospike.hosts=xxx:3000
aerospike.namespace=test
// aerospike configuration class
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(AerospikeConfiguration.AerospikeConfigurationProperties.class)
#EnableAerospikeRepositories(basePackageClassses = TestAeroRepository.class)
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {
private final AerospikeConfigurationProperties aerospikeConfigurationProperties;
#Override
protected Collection<Host> getHosts() {
return Host.parseServiceHosts(aerospikeConfigurationProperties.getHosts());
}
#Override
protected String nameSpace() {
return aerospikeConfigurationProperties.getNamespace();
}
#Data
#Validate
#ConfigurationProperties("aerospike")
public static class AerospikeConfigurationProperties {
#NotEmpty
String hsots;
#NotEmpty
String namespace;
}
}
# Entity class
#Value
#Document
#Builder(toBuilder = true)
#AllArgsConstructor
public class testEntity() {
#Id
int id;
#Field
String name;
#Field
String timestamp;
}
#Repository
public interface TestAeroRepository extends AerospikeRepository<TestEntity, Integer> {
}
public interface TestAeroService {
void save();
}
#Service
#RequiredArgsConstructor
public class TestAeroServiceImpl implements TestAeroService {
private final TestAeroRepository testAeroRepository;
#Override
public void save(TestEntity entity) {
testAeroRepository.save(entity);
}
}
I checked Aerospike client connection has no problem.
But error is occurred when save() method is executed.
org.springframework.cglib.core.ReflectUtils.defineClass(Ljava/lang/String;[BLjava/lang/ClassLoader;Ljava/security/ProtectionDomain;Ljava/lang/Class;)Ljava/lang/Class;
Have to make sets before execute the application? I didn't make sets.
Any problem with my code?
You’re using an old version of spring-data-aerospike (2.0.1.RELEASE was released on April 2019) is there any chance you can upgrade to the latest version? 2.4.2.RELEASE
You can see how to setup a simple spring data aerospike application here: https://medium.com/aerospike-developer-blog/simple-web-application-using-java-spring-boot-aerospike-database-and-docker-ad13795e0089
Please share the entire project’s code and the entire exception.
I would look into:
The configuration class (The Aerospike Beans creation).
The content of the testEntity class - are you using #Id annotation on the primary key field?
Extending the repository class with specifying the testEntity object (… extends AerospikeRepository<testEntity, Object> {) you can see an example in the link I added.
The set is automatically created and takes the name of your object class, which is testEntity in your case. For example, based on your code, if you do not specify a collection in the #Document annotation a set named "testEntity" will automatically be created. I added the #Document(collection = "testEntitys") annotation and all I did was create two set. Once you insert your first record, run the "SHOW SETS" aql command and it will be there. So that's one way to do it.

Why I receive 404 error use Spring MVC framework?

When I send request http://localhost:8080/pets My server response 404!
The code on github: https://github.com/Teemitze/petstore
I build war file. Version spring 2.2.6.RELEASE
#Controller
#RequestMapping("/pets")
public class PetsController {
#Autowired
PetRepository petRepository;
#PostMapping("/addPet")
public void addPet(Pet pet) {
petRepository.save(pet);
}
#GetMapping
#ModelAttribute
public String pets(Model model) {
List<Pet> petList = new ArrayList<>();
petList.add(getPet());
petList.add(getPet());
petList.add(getPet());
model.addAttribute("pets", petList);
return "allPets";
}
public Pet getPet() {
Pet pet = new Pet();
pet.setId(1L);
pet.setName("Мурзик");
pet.setPrice(100);
pet.setBirthday(Date.valueOf("2019-12-12"));
pet.setSex("М");
return pet;
}
}
I checked out your code and found a few issues.
1) Package structure
Move controller, dto, repo packages to the main package (com.petstore)
Since the main application is inside the (com.petstore) package and the controller is outside the package, so it fails to scan the class.
2) Use annotation #Entity for the Pet entity class with #Id for the id property
3) Remove #ModelAttribute from pets() method since you are not binding any method parameter.
After this, I see the /pets
SpringBoot project requires define some configuration conventions that need to be follow in order to start a minimum application.
Some points you have to consider when you want to start a spring boot application.
For example:
Your SpringBootApplication(PetstoreApplication) class should be in the directory level above your other packages so that it can scan all classes.
If you want to use SpringData JPA you have to manage your model class
#Data
#Entity
public class Pet {
#Id
private long id;
private String name;
private String sex;
private Date birthday;
private byte[] photo;
private int price;
}
because it is handled by respository
public interface PetRepository extends CrudRepository<Pet, Long>
Need minimum configuration for Thymeleaf https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
You are making a GET request for a resource "/pets" so no need #ModelAttribute in get mapping method
#GetMapping()
public String allPets(Model model) {
Make sure your html files is under resources/templates directory.
Check out the reference docs
spring mvc
spring data jpa

Customize endpoints with Spring Data REST

I've a project with Spring Boot 1.5.7, Spring Data REST, Hibernate, Spring JPA, Swagger2.
I've two beans like these:
#Entity
public class TicketBundle extends AbstractEntity {
private static final long serialVersionUID = 404514926837058071L;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Note> notes = new ArrayList<>();
.....
}
and
#Entity
public class Note extends AbstractEntity {
private static final long serialVersionUID = -5062313842902549565L;
#Lob
private String text;
...
}
I'm exposing my methods via Repository:
#Transactional
#RepositoryRestResource(excerptProjection = TicketBundleProjection.class)
#PreAuthorize("isAuthenticated()")
public interface TicketBundleRepository extends PagingAndSortingRepository<TicketBundle, Long> {
....
}
so in swagger I see the endpoint in which I'm interested that is needed to load the collection of notes from a specific ticket bundle:
Now, I want to override the default GET /api/v1/ticketBundles/{id}/notes and replace that with my custom method I put in TicketBundleRepository:
#Transactional(readOnly = true)
#RestResource(rel = "ticketBundleNotes", path = "/ticketBundles/{id}/notes")
#RequestMapping(method = RequestMethod.GET, path = "/ticketBundles/{id}/notes")
#Query("SELECT n FROM TicketBundle tb JOIN tb.notes n WHERE tb.id=:id ORDER BY n.createdDate DESC,n.id DESC")
public Page<Note> getNotes(#Param("id") long id, Pageable pageable);
It's very convenient create the query in this way because I need to use Pageable and return a Page. Unfortunately I've two problems at this point.
First problem
The method is mapped on the endpoint /api/v1/ticketBundles/search/ticketBundles/{id}/notes instad of /api/v1/ticketBundles/ticketBundles/{id}/notes
Second problem
When I call the method from swagger I receive an HTTP 404:
The request seems wrong. Seems the path variable is not understood:
curl -X GET --header 'Accept: application/json' 'http://localhost:8080/api/v1/ticketBundles/search/ticketBundles/{id}/notes?id=1'
This is the response from the server:
{
"timestamp": "2017-10-05T14:00:35.563+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/v1/ticketBundles/search/ticketBundles/%7Bid%7D/notes"
}
without any error on the server side.
Is there a way to override the endpoint GET/api/v1/ticketBundles/{id}/notes exposing it through Repository without using a custom controller (using that I would loose the facilities to manage the Pageable)?
Furthermore, what am I doing wrong to get a HTTP 404 in the call I shown above?
I believe you are using incorrect annotations. You would need to annotate your class with #RestController and use #PathVariable on your method instead of #Param. Here is a working sample, you may want to tailor it according to your needs.
#org.springframework.data.rest.webmvc.RepositoryRestController
#org.springframework.web.bind.annotation.RestController
public interface PersonRepository extends org.springframework.data.repository.PagingAndSortingRepository<Person, Long> {
#org.springframework.web.bind.annotation.GetMapping(path = "/people/{id}")
Person findById(#org.springframework.web.bind.annotation.PathVariable("id") Long id);
}

Spring Cassandra Custom Repository for TTL Save

I'm trying use a custom repository to save an entity with a TTL (Time to Live) value. I've have done a lot of searching and read the docs online, but I'm still getting an exception.
Any help gratefully appreciated.
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property saveWithTTL found for type Task!
The snippets are as follows:
Task (the entity):
#Table
public class Task {
#PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String uuid;
private Type type;
private Status status;
private String parentId;
private String body;
}
CassandraDbConfig:
#Configuration
#PropertySource(value = "classpath:cassandra.properties")
#EnableCassandraRepositories(repositoryBaseClass = TTLRepositoryCustomImpl.class)
public class CassandraDbConfig extends DefaultCassandraConfig {
}
TTLRepositoryCustom:
#NoRepositoryBean
public interface TTLRepositoryCustom<T> extends CassandraRepository<T> {
T saveWithTTL(T entity, Integer ttl);
}
TTLRepositoryCustomImpl:
public class TTLRepositoryCustomImpl<T> extends SimpleCassandraRepository<T, MapId>implements TTLRepositoryCustom<T> {
public TTLRepositoryCustomImpl(final CassandraEntityInformation<T, MapId> metadata,
final CassandraOperations operations) {
super(metadata, operations);
}
#Override
public T saveWithTTL(T entity, Integer ttl) {
WriteOptions options = new WriteOptions();
options.setTtl(ttl);
return operations.insert(entity, options);
}
}
TaskDbRepository:
#Repository
public interface TaskDbRepository extends TTLRepositoryCustom<Task> {
}
Full stack trace:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property saveWithTTL found for type Task!
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:77)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:329)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:309)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:272)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:243)
at org.springframework.data.repository.query.parser.Part.<init>(Part.java:76)
at org.springframework.data.repository.query.parser.PartTree$OrPart.<init>(PartTree.java:247)
at org.springframework.data.repository.query.parser.PartTree$Predicate.buildTree(PartTree.java:398)
at org.springframework.data.repository.query.parser.PartTree$Predicate.<init>(PartTree.java:378)
at org.springframework.data.repository.query.parser.PartTree.<init>(PartTree.java:86)
at org.springframework.data.cassandra.repository.query.PartTreeCassandraQuery.<init>(PartTreeCassandraQuery.java:47)
at org.springframework.data.cassandra.repository.support.CassandraRepositoryFactory$CassandraQueryLookupStrategy.resolveQuery(CassandraRepositoryFactory.java:163)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:436)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:221)
You can use something like this in a spring boot application inside a service class (with other 'normal' cassandra repo in place in the application), TTL will be in seconds, I know you are asking for a complete TTL-repo implementation, but this can be handy if you want just to save that with TTL.
#Autowired
private CassandraOperations cassandraOperations;
private void saveWithTTL(Task task)
{
String cql = "insert into task (uuid, body) values ('"+task.getUuid()+"', "+task.getBody()+") USING TTL 86400;";
cassandraOperations.execute(cql);
}

Resources