How to use stable RestHighLevelClient with Elasticsearch? - java

I have searched for so many posts but I couldn't find a proper way to use Elastic Search with spring boot application because I am totally new to elastic search.
My only dependency is:
org.springframework.boot
spring-boot-starter-data-elasticsearch
2.7.3
My config class is:
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.backend.repository.elasticsearchrepository")
#ComponentScan(basePackages = {"com.backend.model.elasticsearchmodel"})
public class ElasticSearchConfig extends AbstractElasticsearchConfiguration {
#Value("${spring.elasticsearch.url}")
public String elasticsearchUrl;
#Value("${spring.elasticsearch.username}")
public String username;
#Value("${spring.elasticsearch.password}")
public String password;
#Bean
#Override
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration config = ClientConfiguration.builder()
.connectedTo(elasticsearchUrl)
.withBasicAuth(username, password)
.build();
return RestClients.create(config).rest();
}
}
Here RestHighLevelClient is shown as deprecated. And my repository class is:
package com.backend.repository.elasticsearchrepository;
import com.backend.model.elasticsearchmodel.EsOffice;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.UUID;
public interface ESOfficeRepository extends ElasticsearchRepository<EsOffice, UUID> {
}
When I call the methods of this repository then it works fine but while storing the data it is returning error message even if it adds the data successfully.
2022-10-15 00:00:15.608 ERROR 51607 --- [nio-8080-exec-2] c.a.a.exception.GlobalExceptionHandler : Unable to parse response body for Response{requestLine=POST /office/_doc?timeout=1m HTTP/1.1, host=http://localhost:9200, response=HTTP/1.1 201 Created}; nested exception is java.lang.RuntimeException: Unable to parse response body for Response{requestLine=POST /office/_doc?timeout=1m HTTP/1.1, host=http://localhost:9200, response=HTTP/1.1 201 Created}
Which POM dependency + what kind of repository should I use and How can I configure it in my config file ? I need these 3 that compatible with each other ?

Spring Data Elasticsearch 4.4 (which is pulled in by Spring Boot 2.7.3) is build with the Elasticsearch libraries in version 7.17, this is problematic when running against an Elasticsearch cluster in version 8. Youhave basically two options:
Downgrade your cluster to version 7.17.6 (the latest 7.17 currently available) i f this is possible.
You can try and see if setting the compatibility headers (see the Spring Data Elasticsearch documentation section 5.3.1 for more info). This should work, but I encountered cases where the response from the cluster still wasn't readable with a 7.17 client. - I had issues opened with Elasticsearch and they were resolved, but there still might be hidden traps.

Related

Debugging SpringBoot MVC service application 404 error

A web application I've been working on recently the past like 2 weeks maybe for whatever reason when I finally tested it - won't seem to even enter the method that I have to return a JSON list of objects. I have included the Jackson library and Spring Boot Web, Tomcat, Data-JPA, Hibernate, MySQL, and a library to allow me to access JSP files. The index.jsp comes up but I almost feel like Spring Boot is giving me that free of charge as it's not even entering that method. I have been having the issue for a few days but trying to resolve it on my own - I found another answer that suggested to put a breakpoint inside one of the Spring classes but when I "debugged" it through Eclipse, it didn't even stop at that class - something about pattern matching - One answer suggested adding a context to the application.properties file - didn't help. I've reduced it to as simple as I think I can get it. Can anyone tell me what I might be doing wrong? Before my code, the project is on Github at: https://github.com/sfulmer/Scheduler.git
Here's my controller:
package net.draconia.schedule.controllers;
import java.util.List;
import net.draconia.schedule.beans.Event;
import net.draconia.schedule.dao.EventDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class ScheduleController
{
private static final Logger logger = LoggerFactory.getLogger(ScheduleController.class);
#Autowired
private EventDAO mObjDAO;
protected EventDAO getDAO()
{
return(mObjDAO);
}
//#GetMapping("/events")
#RequestMapping(value = "events", method = RequestMethod.GET)
public #ResponseBody List<Event> getEvents()
{
logger.debug("I got here");
return(getDAO().getList());
}
#GetMapping("/")
public String index()
{
return("index");
}
}
Here is the DAO interface - I'll show the class if necessary but this is what the controller looks at:
package net.draconia.schedule.dao;
import java.util.List;
import javax.persistence.EntityNotFoundException;
import net.draconia.schedule.beans.Event;
public interface EventDAO
{
public Event getEventById(final long lId) throws EntityNotFoundException;
public List<Event> getList();
public void remove(final Event objEvent);
public void removeById(final long lId);
public Event save(final Event objEvent);
}
The Event class is so long but if I need to include it, I will. The application.properties file is here:
spring.datasource.url = jdbc:mysql://localhost:3306/schedule
spring.datasource.username = root
spring.datasource.password = R3g1n# M1lL$ 1$ My Qu3eN!
spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp
server.servlet.contextPath=/scheduler
and here is my Application class(with the SpringBootApplication annotation):
package net.draconia.schedule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#SpringBootApplication(scanBasePackages = {"net.draconia.schedule.controller"})
public class ScheduleApp implements WebMvcConfigurer
{
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
{
return(builder.sources(ScheduleApp.class));
}
public static void main(String[] args)
{
SpringApplication.run(ScheduleApp.class, args);
}
}
I'm relatively new to Spring Boot but haven't ever ran into this problem ever before as I work with it at work and it works fine but we use entirely REST services there and I am using JSP files as well as sorta end-points that respond with JSON but you can't respond from REST services with JSP views so unfortunately I can't copy work's project to get that working or I would sigh Any thoughts on how I can get this working or what I am omitting?
My guess is that you're mixing things from Spring and Spring boot, and that's getting problems on loading beans, as you're probably changing the annotations load order or loading other beans rather than spring boot defaults as expected. For example, you implements WebMvcConfigurer, but you aren't providing any WebMvc Configuration, like a ViewResolver bean
My advice is to follow this guide: https://spring.io/guides/gs/spring-boot/
and use only the annotations from spring boot if using spring boot, or spring if using spring (they're similar, but not exactly the same, configuration is different).
Anyways, you can check loaded beans in Spring application context (Inject it in Application class) with ctx.getBeanDefinitionNames() method and see if your controller is there (i guess not).
By looking into code, my first impression is that, you have some typo in here:
#SpringBootApplication(scanBasePackages = {"net.draconia.schedule.controller"})
Your controller class package name has net.draconia.schedule.controllers.
So can you please correct your scanBasePackages with proper package name.
If that is not the case, please update full stack trace along with GET request which you are submitting into application. Will take a look & update answer accordingly.

Field repository in '' required a bean named 'entityManagerFactory' that could not be found

So I've been learning Spring for a few weeks. I am trying to make a simple project involving a Controller -> Service -> Repository -> Database pattern.
I started having this problem, but could not find the solution to it. I stumbled upon some similar problems online with the same error, but none of them gave my solution, everything seems normal in my project.
This is the error on output:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field repository in com.Library.services.LibraryService required a bean named 'entityManagerFactory' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean named 'entityManagerFactory' in your configuration.
This are the files of my project (file tree):
This is my code:
Main class:
package com.Library;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#EntityScan("com.Library.services")
#EnableJpaRepositories("com.Library.repositories")
public class LibraryApplication {
public static void main(String[] args) {
SpringApplication.run(LibraryApplication.class, args);
}
}
Model:
package com.Library.models.dtos;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
#Getter
#Setter
#Entity
public class BookCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String Name;
}
Repository:
package com.Library.repositories;
import com.Library.models.dtos.BookCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public interface BookCategoryRepository extends JpaRepository<BookCategory,Long> {
List<BookCategory> findAll();
}
Service:
package com.Library.services;
import com.Library.models.dtos.BookCategory;
import com.Library.repositories.BookCategoryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
#Service
public class LibraryService {
#Autowired
BookCategoryRepository repository;
public List<BookCategory> getAllCategories() {
return repository.findAll();
}
}
Controller:
package com.Library.controllers;
import com.Library.models.dtos.BookCategory;
import com.Library.services.LibraryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#CrossOrigin(origins = "*", allowedHeaders = "*")
#RestController
public class LibraryController{
#Autowired
LibraryService libraryService;
public List<BookCategory> getAllCategories() {
return libraryService.getAllCategories();
}
}
What could be the issue?
This bean definition is usually provided automatically by Spring Boot Auto-Configuration. The spring reference manual explains how to diagnose such issues:
The Spring Boot auto-configuration tries its best to “do the right thing”, but sometimes things fail, and it can be hard to tell why.
There is a really useful ConditionEvaluationReport available in any Spring Boot ApplicationContext. You can see it if you enable DEBUG logging output. If you use the spring-boot-actuator (see the Actuator chapter), there is also a conditions endpoint that renders the report in JSON. Use that endpoint to debug the application and see what features have been added (and which have not been added) by Spring Boot at runtime.
Many more questions can be answered by looking at the source code and the Javadoc. When reading the code, remember the following rules of thumb:
Look for classes called *AutoConfiguration and read their sources. Pay special attention to the #Conditional* annotations to find out what features they enable and when. Add --debug to the command line or a System property -Ddebug to get a log on the console of all the auto-configuration decisions that were made in your app. In a running application with actuator enabled, look at the conditions endpoint (/actuator/conditions or the JMX equivalent) for the same information.
In your case, a simple full text search finds that Hibernate is auto-configured by org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, which is declared as follows:
#AutoConfiguration(after = { DataSourceAutoConfiguration.class })
#ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
#EnableConfigurationProperties(JpaProperties.class)
#Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {
}
As you can tell from the ConditionalOnClass annotation, this configuration is only applied if your classpath contains the classes LocalContainerEntityManagerFactoryBean from spring-orm-jpa, EntityManager from the JPA spec, and SessionImplementor from the hibernate jar.
Most likely, you are missing one of these JAR files (maven dependencies), or have the wrong version of one. The ConditionEvaluationReport should tell you which, and the precise package names to check for.

/actuator/prometheus missing in #SpringbootTest

I'm using springbooot 2.4.0 and I added the following dependencies for enabling prometheus metrics:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
then in my application.properties I have the following properties
management.endpoints.web.exposure.include=*
management.metrics.enable.all=true
I'm trying to run a simple integration test to see my custom metrics appearing at /actuator/prometheus endpoint. Below the code
package com.example.demo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import static io.restassured.RestAssured.given;
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IntegrationTest {
#LocalServerPort
private int port;
private String baseUrl;
#BeforeEach
public void setup() {
baseUrl = "http://localhost:" + port;
}
#Test
public void metricsEndpoint() throws Exception {
given().when().get(baseUrl + "/demo/actuator/prometheus")
.then()
.statusCode(200);
}
}
The error I get here is
java.lang.AssertionError: 1 expectation failed.
Expected status code <200> but was <404>.
while if I repeat the same request for any other endpoint provided by springboot actuator I correctly geth the response, for example I tried /actuator/health, /actuator/info, /actuator/metrics etc..
This happens only during integration tests with #Springboot annotation and this is strange because if I run my application and make a request with postman to the address localhost:8080/actuator/prometheus I correctly get a response.
It is like the prometheus registry is not loaded during tests.
Can anyone help?
Thanks in advance.
EDIT: the solution is the one suggested by Johannes Klug. Adding the annotation #AutoConfigureMetrics solved my problem
I faced the same issue. After some tracing through spring-context ConditionEvaluator, I found that the newly introduced #ConditionalOnEnabledMetricsExport("prometheus") condition on PrometheusMetricsExportAutoConfiguration prevented the endpoint from loading.
This is intended behavior due to https://github.com/spring-projects/spring-boot/pull/21658 and impacts spring-boot 2.4.x
Fix:
add #AutoConfigureMetrics to your test
#AutoConfigureMetrics
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IntegrationTest {
It's happening to me too, with Spring Boot 2.4.1
The only thing that I can think of, is that the endpoints that are included in #SpringBootTest are using the #Endpoint annotation.
For example:
#Endpoint(id = "metrics")
public class MetricsEndpoint {}
#Endpoint(id = "loggers")
public class LoggersEndpoint {}
And the prometheus endpoint is using the #WebEndpoint annotation
#WebEndpoint(id = "prometheus")
public class PrometheusScrapeEndpoint {}
I didn't find if this is how it should work, or is a probable bug
I see in your test, you are appending "demo" before actual actuator path.
given().when().get(baseUrl + "/demo/actuator/prometheus")
.then()
.statusCode(200);
The correct url should be baseUrl+"/actuator/prometheus".
Since Spring Boot 3.0.0 #AutoConfigureMetrics has been deprecated and will be removed in 3.2.0, it is recommended to use #AutoConfigureObservability instead

How do I KMS decrypt the password in the application.properties first before being used in spring boot

my first time using AWS and spring boot together.
I have my db credentials set up in application.properties.
But I still need to KMS decrypt the password.
How do I do that in spring boot framework?
First of all, you should include the "zalando/spring-cloud-config-aws-kms" dependancy to your project, for more details about the project check this link: "https://github.com/zalando/spring-cloud-config-aws-kms"
You should be careful about the choice of versions, for example if you are using Spring Cloud Greenwich + Spring Boot 2.1 the zalando dependency version should be 4.1
Now suppose your spring-boot project is a maven project, then you should have something like this:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>spring-cloud-config-aws-kms</artifactId>
<version>4.1</version>
</dependency>
Second, your encrypted password value in the application.properties should begin with {cipher}, example (the cipher shown below is not a valid one):
DataBase.Password = {cipher}UmjDPAmJr78ypSphQycO9DAQECAHgC4i08YQPW
Finally, because you have the spring-cloud-config-aws-kms in your project classPath you have only to inject the value of your encrypted password in your classes when needed via the #Value annotaion, as an example:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
public class Example {
//reads the encrypted password, decrypts it
// and injects it in the field DataBasePassword
#Value("${DataBase.Password}")
private String DataBasePassword;
#RequestMapping("/")
public String decryptPassword() {
return DataBasePassword;
}
public static void main(String[] args) {
SpringApplication.run(Example.class, args);
}
}
Launch this Spring-Boot application, open a browser and type the url "http://localhost:8080/" to see the result.
This answer is inspired from this project "https://github.com/kinow/spring-boot-aws-kms-configuration". Hope that will be helpful :)

Using SpringFox to document my Spring RestController

I'm trying to use SpringFox to document a #RestController in my application but so far I've been met with stout resistance.
I've been following this example but whenever I start I get the following exception:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'documentationPluginsBootstrapper'
I know that's not much to go on so I managed to reproduce it on a pretty simple standalone application:
package example.swagger;
import org.springframework.boot.builder.SpringApplicationBuilder;
public class Main {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(AppConfig.class);
builder.child(SwaggerConfiguration.class).run(args);
}
}
The configuration classes used are as follows:
package example.swagger;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
public class AppConfig {
}
And finally:
package example.swagger;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SwaggerConfiguration {
}
I've been struggling with this for days. Any help would be most appreciated! ;)
If I were to take a guess, it's probably because you are not running with the latest version of springfox. Could you try upgrading to 2.5.0. It may fix your problem.
PS: Don't have the link to the issue that fixes this handy, but I'll update this answer when I do.

Categories

Resources