From 7954caad63249c17ab3b77e5932b9427df92c598 Mon Sep 17 00:00:00 2001 From: shroffk Date: Tue, 4 Aug 2026 09:30:44 -0400 Subject: [PATCH 01/17] Phased upgrade of springboot --- services/alarm-config-logger/pom.xml | 6 ++++-- services/alarm-logger/pom.xml | 6 ++++-- .../org/phoebus/alarm/logging/purge/ElasticIndexPurger.java | 2 +- .../org/phoebus/alarm/logging/rest/SearchController.java | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/services/alarm-config-logger/pom.xml b/services/alarm-config-logger/pom.xml index 53bbfc6b59..a59189f5d3 100644 --- a/services/alarm-config-logger/pom.xml +++ b/services/alarm-config-logger/pom.xml @@ -7,6 +7,8 @@ 21 + + 3.5.16 service-alarm-config-logger ${project.groupId}:${project.artifactId} @@ -91,8 +93,8 @@ jackson-annotations ${jackson.annotations.version} - - + + org.slf4j slf4j-jdk14 1.7.28 diff --git a/services/alarm-logger/pom.xml b/services/alarm-logger/pom.xml index ea56a8b29d..3f6737afaa 100644 --- a/services/alarm-logger/pom.xml +++ b/services/alarm-logger/pom.xml @@ -7,6 +7,8 @@ 21 + + 3.5.16 service-alarm-logger ${project.groupId}:${project.artifactId} @@ -141,8 +143,8 @@ org.springdoc - springdoc-openapi-ui - 1.7.0 + springdoc-openapi-starter-webmvc-ui + 2.8.17 diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java index f5a22e3e3b..33aee84ade 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java @@ -38,7 +38,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java index fa0b6d5937..33141d0048 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java @@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.LinkedHashMap; From 5723ffc8053ec539acb8f31668eddaf08db4f02b Mon Sep 17 00:00:00 2001 From: shroffk Date: Tue, 4 Aug 2026 09:45:50 -0400 Subject: [PATCH 02/17] update save and restore - spring 3, jakarta --- app/save-and-restore/model/pom.xml | 6 +-- services/save-and-restore/pom.xml | 18 ++++---- .../web/config/AcceptHeaderResolver.java | 2 +- .../web/config/WebConfiguration.java | 2 +- .../web/config/WebSecurityConfig.java | 43 +++++++++---------- .../web/controllers/BaseController.java | 4 +- .../web/controllers/HelpResource.java | 2 +- .../web/config/ControllersTestConfig.java | 10 ++--- .../web/controllers/SearchControllerTest.java | 6 +-- 9 files changed, 45 insertions(+), 48 deletions(-) diff --git a/app/save-and-restore/model/pom.xml b/app/save-and-restore/model/pom.xml index a8c3a05d54..ea17f1a944 100644 --- a/app/save-and-restore/model/pom.xml +++ b/app/save-and-restore/model/pom.xml @@ -72,11 +72,11 @@ ${tools.jackson.version} - + org.glassfish - javax.json - 1.1.4 + jakarta.json + 2.0.1 diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index b672def0ce..44d36b15b3 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -14,6 +14,7 @@ 4.4 + 3.5.16 @@ -52,8 +53,8 @@ org.springdoc - springdoc-openapi-ui - 1.7.0 + springdoc-openapi-starter-webmvc-ui + 2.8.17 @@ -160,6 +161,12 @@ 2.0.1 + + + org.glassfish + jakarta.json + 2.0.1 + org.springframework.boot spring-boot-starter-logging @@ -171,13 +178,6 @@ - - - org.glassfish - javax.json - 1.1.4 - - org.springframework diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AcceptHeaderResolver.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AcceptHeaderResolver.java index 17c9a8c9f0..1389682451 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AcceptHeaderResolver.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/AcceptHeaderResolver.java @@ -20,7 +20,7 @@ import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.Locale; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java index 63fe8a2dc1..1530c661f3 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java @@ -27,7 +27,7 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.Scope; -import javax.servlet.ServletContext; +import jakarta.servlet.ServletContext; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java index 2f8544104d..fb97d28aad 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java @@ -13,8 +13,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; @@ -30,13 +29,15 @@ import org.springframework.security.web.SecurityFilterChain; import tools.jackson.databind.json.JsonMapper; +import static org.springframework.security.config.Customizer.withDefaults; + /** * {@link Configuration} class setting up authentication/authorization depending on the * auth.impl application property. */ @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) +@EnableMethodSecurity(prePostEnabled = true) @SuppressWarnings("unused") public class WebSecurityConfig { @@ -189,8 +190,8 @@ public String authenticationImplementation(){ public WebSecurityCustomizer ignoringCustomizer() { return web -> { // The below lists exceptions for authentication. - web.ignoring().antMatchers(HttpMethod.GET, "/**"); - web.ignoring().antMatchers(HttpMethod.POST, "/**/login*"); + web.ignoring().requestMatchers(HttpMethod.GET, "/**"); + web.ignoring().requestMatchers(HttpMethod.POST, "/**/login*"); }; } @@ -202,9 +203,9 @@ public WebSecurityCustomizer ignoringCustomizer() { */ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf().disable(); - http.authorizeRequests().anyRequest().authenticated(); - http.httpBasic(); + http.csrf(csrf -> csrf.disable()); + http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()); + http.httpBasic(withDefaults()); return http.build(); } @@ -255,17 +256,17 @@ public LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSou myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern); myAuthPopulator.setSearchSubtree(true); myAuthPopulator.setIgnorePartialResultException(true); - LdapAuthenticationProviderConfigurer configurer = new LdapAuthenticationProviderConfigurer(); - if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) { - configurer.userDnPatterns(ldap_user_dn_pattern); - } - if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) { - configurer.userSearchFilter(ldap_user_search_filter); - } - if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) { - configurer.userSearchBase(ldap_user_search_base); - } - configurer.contextSource(contextSource); +// LdapAuthenticationProviderConfigurer configurer = new LdapAuthenticationProviderConfigurer(); +// if (ldap_user_dn_pattern != null && !ldap_user_dn_pattern.isEmpty()) { +// configurer.userDnPatterns(ldap_user_dn_pattern); +// } +// if (ldap_user_search_filter != null && !ldap_user_search_filter.isEmpty()) { +// configurer.userSearchFilter(ldap_user_search_filter); +// } +// if (ldap_user_search_base != null && !ldap_user_search_base.isEmpty()) { +// configurer.userSearchBase(ldap_user_search_base); +// } +// configurer.contextSource(contextSource); return myAuthPopulator; } @@ -347,9 +348,7 @@ public ObjectMapper objectMapper() { */ @Bean public RoleHierarchy roleHierarchy() { - RoleHierarchyImpl hierarchy = new RoleHierarchyImpl(); - hierarchy.setHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase()); - return hierarchy; + return RoleHierarchyImpl.fromHierarchy("ROLE_" + roleAdmin.toUpperCase() + " > ROLE_" + roleUser.toUpperCase()); } /** diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java index d031cb6e1b..b732ed5c6c 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java @@ -25,8 +25,8 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java index 5b8d8b8697..64359e43db 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java @@ -28,7 +28,7 @@ import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java index a6e5bc57af..ff61d72dbc 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java @@ -36,10 +36,10 @@ import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.messaging.simp.user.SimpUserRegistry; import org.springframework.mock.web.MockServletContext; -import org.springframework.util.Base64Utils; +import java.util.Base64; import org.springframework.web.socket.WebSocketSession; -import javax.servlet.ServletContext; +import jakarta.servlet.ServletContext; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -111,17 +111,17 @@ public SearchUtil searchUtil() { @Bean("userAuthorization") public String userAuthorization() { - return "Basic " + Base64Utils.encodeToString((demoUser + ":" + demoUserPassword).getBytes()); + return "Basic " + Base64.getEncoder().encodeToString((demoUser + ":" + demoUserPassword).getBytes()); } @Bean("adminAuthorization") public String adminAuthorization() { - return "Basic " + Base64Utils.encodeToString((demoAdmin + ":" + demoAdminPassword).getBytes()); + return "Basic " + Base64.getEncoder().encodeToString((demoAdmin + ":" + demoAdminPassword).getBytes()); } @Bean("readOnlyAuthorization") public String readOnlyAuthorization() { - return "Basic " + Base64Utils.encodeToString((demoReadOnly + ":" + demoReadOnlyPassword).getBytes()); + return "Basic " + Base64.getEncoder().encodeToString((demoReadOnly + ":" + demoReadOnlyPassword).getBytes()); } @Bean diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java index ad6b7fe40d..fe14f94b36 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java @@ -42,9 +42,7 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.util.LinkedMultiValueMap; - -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.MultivaluedMap; +import org.springframework.util.MultiValueMap; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -96,7 +94,7 @@ public void testSearch() throws Exception { @Test public void testSearchForPVs() { - MultivaluedMap> searchParams = new MultivaluedHashMap<>(); + MultiValueMap> searchParams = new LinkedMultiValueMap<>(); searchParams.put("type", List.of(List.of(NodeType.CONFIGURATION.toString()))); searchParams.put("pvs", List.of(List.of("abc"))); From 6188eab45141dfa803c031bd4e2d6fc9caa85f7b Mon Sep 17 00:00:00 2001 From: shroffk Date: Tue, 4 Aug 2026 10:41:02 -0400 Subject: [PATCH 03/17] spring 4.1 --- services/alarm-config-logger/pom.xml | 4 ++-- services/alarm-logger/pom.xml | 4 ++-- .../logging/rest/SearchControllerTest.java | 23 +++++++++++-------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/services/alarm-config-logger/pom.xml b/services/alarm-config-logger/pom.xml index a59189f5d3..825f0cc74f 100644 --- a/services/alarm-config-logger/pom.xml +++ b/services/alarm-config-logger/pom.xml @@ -7,8 +7,8 @@ 21 - - 3.5.16 + + 4.1.0 service-alarm-config-logger ${project.groupId}:${project.artifactId} diff --git a/services/alarm-logger/pom.xml b/services/alarm-logger/pom.xml index 3f6737afaa..8f85792902 100644 --- a/services/alarm-logger/pom.xml +++ b/services/alarm-logger/pom.xml @@ -7,8 +7,8 @@ 21 - - 3.5.16 + + 4.1.0 service-alarm-logger ${project.groupId}:${project.artifactId} diff --git a/services/alarm-logger/src/test/java/org/phoebus/alarm/logging/rest/SearchControllerTest.java b/services/alarm-logger/src/test/java/org/phoebus/alarm/logging/rest/SearchControllerTest.java index 83400e8b9c..4b919ea3c8 100644 --- a/services/alarm-logger/src/test/java/org/phoebus/alarm/logging/rest/SearchControllerTest.java +++ b/services/alarm-logger/src/test/java/org/phoebus/alarm/logging/rest/SearchControllerTest.java @@ -4,28 +4,31 @@ package org.phoebus.alarm.logging.rest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.junit.jupiter.api.Assertions.*; -@ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = ControllersTestConfig.class) -@WebMvcTest(SearchController.class) +/** + * Unit test for {@link SearchController} using standalone MockMvc setup. + * Migrated from @WebMvcTest (removed in Spring Boot 4.x) to + * MockMvcBuilders.standaloneSetup() which requires no Spring context. + */ public class SearchControllerTest { - @Autowired private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(new SearchController()).build(); + } + @Test public void testRedirectSwagger() throws Exception { MockHttpServletRequestBuilder request = get("/swagger-ui"); From ad7a6416efab8771e93d1d20e252db3b380f73ec Mon Sep 17 00:00:00 2001 From: shroffk Date: Tue, 4 Aug 2026 15:18:54 -0400 Subject: [PATCH 04/17] now try to make save and restore to spring4.1 --- app/save-and-restore/model/pom.xml | 11 ++++++++++- services/save-and-restore/pom.xml | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/save-and-restore/model/pom.xml b/app/save-and-restore/model/pom.xml index ea17f1a944..39eb262cd3 100644 --- a/app/save-and-restore/model/pom.xml +++ b/app/save-and-restore/model/pom.xml @@ -72,7 +72,16 @@ ${tools.jackson.version} - + + + org.glassfish + javax.json + 1.1.4 + + org.glassfish jakarta.json diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index 44d36b15b3..0928cd2d8e 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -14,7 +14,7 @@ 4.4 - 3.5.16 + 4.1.0 From b03359b7e90a9e470ada6f07fe8c869e6e5d4929 Mon Sep 17 00:00:00 2001 From: shroffk Date: Tue, 4 Aug 2026 15:56:56 -0400 Subject: [PATCH 05/17] start migration to new spring security API --- .../web/config/WebSecurityConfig.java | 37 ++++++++----------- .../controllers/AuthenticationController.java | 4 +- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java index fb97d28aad..5191b96815 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebSecurityConfig.java @@ -11,9 +11,11 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.ObjectPostProcessor; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; @@ -277,7 +279,7 @@ public LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSou */ @Bean @ConditionalOnProperty(name = "auth.impl", havingValue = "ad") - public AuthenticationManager authenticationProvider() throws Exception { + public AuthenticationManager authenticationProvider() { ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ad_domain, ad_url); adProvider.setConvertSubErrorCodesToExceptions(true); @@ -286,33 +288,24 @@ public AuthenticationManager authenticationProvider() throws Exception { SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper(); simpleAuthorityMapper.setConvertToUpperCase(true); adProvider.setAuthoritiesMapper(simpleAuthorityMapper); - return new AuthenticationManagerBuilder(new ObjectPostProcessor<>() { - @Override - public O postProcess(O object) { - return object; - } - }).authenticationProvider(adProvider).build(); + return new ProviderManager(adProvider); } /** * Created only if application property auth.impl = demo. - * @param auth Injected by Spring * @return A {@link AuthenticationManager} object - * @throws Exception on error */ @Bean @ConditionalOnProperty(name = "auth.impl", havingValue = "demo") - public AuthenticationManager demoAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception { - return new AuthenticationManagerBuilder(new ObjectPostProcessor<>() { - @Override - public O postProcess(O object) { - return object; - } - }).inMemoryAuthentication() - .passwordEncoder(encoder()) - .withUser(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).and() - .withUser(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).and() - .withUser(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles().and().and().build(); + public AuthenticationManager demoAuthenticationManager() { + InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager( + User.withUsername(demoAdmin).password(encoder().encode(demoAdminPassword)).roles(roleAdmin()).build(), + User.withUsername(demoUser).password(encoder().encode(demoUserPassword)).roles(roleUser()).build(), + User.withUsername(demoReadOnly).password(encoder().encode(demoReadOnlyPassword)).roles().build() + ); + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService); + provider.setPasswordEncoder(encoder()); + return new ProviderManager(provider); } /** diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java index 7a87af6a8e..917162bfbf 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthenticationController.java @@ -62,9 +62,7 @@ public ResponseEntity login(@RequestBody LoginCredentials loginCredent authentication = authenticationManager.authenticate(authentication); } catch (AuthenticationException e) { Logger.getLogger(AuthenticationController.class.getName()).log(Level.WARNING, "Unable to authenticate user " + loginCredentials.username(), e); - return new ResponseEntity<>( - null, - HttpStatus.UNAUTHORIZED); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } List roles = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority).collect(Collectors.toList()); From e0d2e1f1d3582debe73b7f21405223bee3621898 Mon Sep 17 00:00:00 2001 From: shroffk Date: Tue, 4 Aug 2026 16:53:42 -0400 Subject: [PATCH 06/17] updating save and restore tests for springboot 4.1 --- .../AbstractElasticsearchIT.java | 4 +-- .../impl/elasticsearch/ElasticTestConfig.java | 25 ++++++++++++++--- .../elasticsearch/ElasticsearchDAOTest.java | 10 +------ .../web/config/ControllersTestConfig.java | 7 ++++- .../AppMetaDataControllerTest.java | 21 +++++++++------ .../controllers/ComparisonControllerTest.java | 23 +++++++++------- ...positeSnapshotControllerPermitAllTest.java | 22 +++++++++------ .../CompositeSnapshotControllerTest.java | 21 +++++++++------ .../ConfigurationControllerPermitAllTest.java | 21 +++++++++------ .../ConfigurationControllerTest.java | 21 +++++++++------ .../FilterControllerPermitAllTest.java | 21 +++++++++------ .../web/controllers/FilterControllerTest.java | 21 +++++++++------ .../web/controllers/HelpResourceTest.java | 12 +-------- .../NodeControllerPermitAllTest.java | 25 ++++++++++------- .../web/controllers/NodeControllerTest.java | 25 ++++++++++------- .../web/controllers/SearchControllerTest.java | 21 +++++++++------ .../SnapshotControllerPermitAllTest.java | 23 +++++++++------- .../controllers/SnapshotControllerTest.java | 23 +++++++++------- .../SnapshotRestorerControllerTest.java | 26 ++++++++++-------- .../StructureControllerPermitAllTest.java | 23 +++++++++------- .../controllers/StructureControllerTest.java | 27 +++++++++++-------- .../TagControllerPermitAllTest.java | 21 +++++++++------ .../web/controllers/TagControllerTest.java | 21 +++++++++------ .../TakeSnapshotControllerTest.java | 23 +++++++++------- 24 files changed, 294 insertions(+), 193 deletions(-) diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/AbstractElasticsearchIT.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/AbstractElasticsearchIT.java index 1a9bd0f46f..cb10afc70e 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/AbstractElasticsearchIT.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/AbstractElasticsearchIT.java @@ -11,9 +11,9 @@ import org.phoebus.service.saveandrestore.persistence.config.ElasticConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Profile; import org.springframework.core.env.Environment; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; @@ -39,7 +39,7 @@ @SpringBootTest @ContextConfiguration(classes = ElasticConfig.class) @TestPropertySource(locations = "classpath:test_application.properties") -@Profile("IT") +@ActiveProfiles("IT") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public abstract class AbstractElasticsearchIT { diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java index 90399fb6b3..62e7c7c0f7 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java @@ -2,17 +2,21 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import org.mockito.Mockito; +import org.phoebus.service.saveandrestore.search.SearchUtil; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration -@ComponentScan(basePackages = {"org.phoebus.service.saveandrestore"}) -@SuppressWarnings("unused") @Profile("!IT") +@SuppressWarnings("unused") public class ElasticTestConfig { + @Bean + public ElasticsearchDAO elasticsearchDAO() { + return new ElasticsearchDAO(); + } + @Bean({"client"}) public ElasticsearchClient getClient() { @@ -29,6 +33,21 @@ public ConfigurationDataRepository configurationDataRepository(){ return Mockito.mock(ConfigurationDataRepository.class); } + @Bean + public FilterRepository filterRepository() { + return Mockito.mock(FilterRepository.class); + } + + @Bean + public CompositeSnapshotDataRepository compositeSnapshotDataRepository() { + return Mockito.mock(CompositeSnapshotDataRepository.class); + } + + @Bean + public SearchUtil searchUtil() { + return Mockito.mock(SearchUtil.class); + } + @SuppressWarnings("unused") @Bean public SnapshotDataRepository snapshotDataRepository(){ diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java index 606c9d4884..f9f44ff6b6 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchDAOTest.java @@ -26,14 +26,9 @@ import org.phoebus.service.saveandrestore.model.ESTreeNode; import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ElasticsearchDAO.NodeNameComparator; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Profile; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import java.util.Arrays; import java.util.List; @@ -45,11 +40,8 @@ import static org.mockito.Mockito.when; @ExtendWith(SpringExtension.class) -@EnableConfigurationProperties -@ContextHierarchy({@ContextConfiguration(classes = {ElasticTestConfig.class})}) -@TestExecutionListeners({DependencyInjectionTestExecutionListener.class}) +@ContextConfiguration(classes = {ElasticTestConfig.class}) @TestPropertySource(locations = "classpath:test_application.properties") -@Profile("IT") public class ElasticsearchDAOTest { @Autowired diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java index ff61d72dbc..fe097f5eb9 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java @@ -43,7 +43,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + @TestConfiguration +@EnableWebMvc @ComponentScan(basePackages = "org.phoebus.service.saveandrestore.web.controllers") @SuppressWarnings("unused") @Profile("!IT") @@ -131,7 +134,9 @@ public ExecutorService executorService() { @Bean public SnapshotUtil snapshotUtil() { - return new SnapshotUtil(); + // Return a mock so tests can stub it with when(...); previously @MockBean was used + // but @MockBean was removed in Spring Boot 4.x. + return Mockito.mock(SnapshotUtil.class); } @Bean diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java index 142d9e90da..8349835d20 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/AppMetaDataControllerTest.java @@ -17,18 +17,18 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -41,17 +41,22 @@ * @author georgweiss * Created 16 May 2019 */ -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(AppMetaDataControllerTest.class) -@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") @SuppressWarnings("unused") public class AppMetaDataControllerTest { @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Value("${app.name}") private String appName; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java index 2069903d05..3e8e534a45 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java @@ -3,6 +3,10 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; @@ -11,7 +15,6 @@ import org.epics.vtype.Time; import org.epics.vtype.VDouble; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.applications.saveandrestore.model.ComparisonResult; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; import org.phoebus.applications.saveandrestore.model.ConfigPv; @@ -24,11 +27,8 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -44,18 +44,23 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") -@WebMvcTest(ComparisonController.class) public class ComparisonControllerTest { @Autowired private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + private final ObjectMapper objectMapper = new ObjectMapper(); @Test @@ -189,4 +194,4 @@ public void testCompositeSnapshot() throws Exception{ reset(nodeDAO); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java index bd9774840b..f1caf56b77 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerPermitAllTest.java @@ -18,11 +18,15 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; @@ -32,12 +36,8 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -52,9 +52,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(CompositeSnapshotController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, + webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") public class CompositeSnapshotControllerPermitAllTest { @@ -68,8 +67,15 @@ public class CompositeSnapshotControllerPermitAllTest { private String demoUser; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + private final ObjectMapper objectMapper = new ObjectMapper(); private static CompositeSnapshot compositeSnapshot; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java index 198d9d67dd..7d3611856e 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotControllerTest.java @@ -18,13 +18,17 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; @@ -37,12 +41,8 @@ import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.phoebus.service.saveandrestore.websocket.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -60,9 +60,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(CompositeSnapshotController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") public class CompositeSnapshotControllerTest { @@ -82,8 +80,15 @@ public class CompositeSnapshotControllerTest { private String demoUser; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private WebSocketService webSocketService; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java index 41c419497b..51221a9868 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerPermitAllTest.java @@ -18,11 +18,15 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.Configuration; import org.phoebus.applications.saveandrestore.model.ConfigurationData; @@ -32,12 +36,8 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -50,9 +50,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(ConfigurationController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") public class ConfigurationControllerPermitAllTest { @@ -60,8 +58,15 @@ public class ConfigurationControllerPermitAllTest { private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private ObjectMapper objectMapper; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java index 4a8e7acaed..0d546eae80 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationControllerTest.java @@ -18,11 +18,15 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.Comparison; import org.phoebus.applications.saveandrestore.model.ComparisonMode; @@ -37,12 +41,8 @@ import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.phoebus.service.saveandrestore.websocket.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -58,9 +58,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(ConfigurationController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") public class ConfigurationControllerTest { @@ -68,8 +66,15 @@ public class ConfigurationControllerTest { private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private ObjectMapper objectMapper; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java index 7e5961721a..36dd4a93f9 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerPermitAllTest.java @@ -18,22 +18,22 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -48,9 +48,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(FilterController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") public class FilterControllerPermitAllTest { @@ -58,8 +56,15 @@ public class FilterControllerPermitAllTest { private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private ObjectMapper objectMapper; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java index 9d1cc79b22..db3409dbc0 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/FilterControllerTest.java @@ -18,11 +18,15 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.core.websocket.common.WebSocketMessage; @@ -31,12 +35,8 @@ import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.phoebus.service.saveandrestore.websocket.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -55,9 +55,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(FilterController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") public class FilterControllerTest { @@ -65,8 +63,15 @@ public class FilterControllerTest { private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private ObjectMapper objectMapper; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java index 995e84570a..a47e4e2bb5 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java @@ -20,24 +20,14 @@ import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; -import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -@ExtendWith(SpringExtension.class) -@WebMvcTest(HelpResource.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) @TestPropertySource(locations = "classpath:test_application.properties") public class HelpResourceTest{ @@ -68,4 +58,4 @@ public void testGetCheatSheetUnsupportedHelpType() throws Exception{ MockHttpServletRequestBuilder request = get("/help/unsupported"); mockMvc.perform(request).andExpect(status().isNotFound()); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java index 9a2712ab4a..63c843df29 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerPermitAllTest.java @@ -17,23 +17,23 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -47,10 +47,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") -@WebMvcTest(NodeController.class) /** * Main purpose of the tests in this class is to verify that REST end points are * maintained, i.e. that URLs are not changed and that they return the correct @@ -65,8 +63,15 @@ public class NodeControllerPermitAllTest { private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + private static Node folderFromClient; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -81,7 +86,7 @@ public class NodeControllerPermitAllTest { private String readOnlyAuthorization; @BeforeAll - public static void setUp() { + public static void initData() { folderFromClient = Node.builder().name("SomeFolder").userName("myusername").uniqueId("11").build(); } @@ -205,4 +210,4 @@ public void testUpdateNode() throws Exception { .content(objectMapper.writeValueAsString(node)); mockMvc.perform(request).andExpect(status().isUnauthorized()); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java index 78d22be804..e4a20111f2 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/NodeControllerTest.java @@ -17,13 +17,17 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.phoebus.applications.saveandrestore.model.Comparison; @@ -42,12 +46,8 @@ import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.phoebus.service.saveandrestore.websocket.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -76,18 +76,23 @@ * * @author Georg Weiss, European Spallation Source */ -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") -@WebMvcTest(NodeController.class) public class NodeControllerTest { @Autowired private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private WebSocketService webSocketService; @@ -113,7 +118,7 @@ public class NodeControllerTest { private String readOnlyAuthorization; @BeforeAll - public static void setUp() { + public static void initData() { config1 = Node.builder().nodeType(NodeType.CONFIGURATION).uniqueId("a") .userName("myusername").build(); @@ -829,4 +834,4 @@ public void testRootNodeChildNodes() throws Exception { }); assertEquals(1, nodes.size()); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java index fe14f94b36..70262fa414 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SearchControllerTest.java @@ -18,11 +18,15 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import co.elastic.clients.elasticsearch.core.SearchRequest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.NodeType; @@ -33,11 +37,7 @@ import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -52,9 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(SearchController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") public class SearchControllerTest { @@ -62,8 +60,15 @@ public class SearchControllerTest { private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private ObjectMapper objectMapper; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java index c4ae3d0d46..869a97b253 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerPermitAllTest.java @@ -18,10 +18,14 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.phoebus.applications.saveandrestore.model.Node; @@ -31,12 +35,8 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -50,10 +50,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") -@WebMvcTest(SnapshotController.class) public class SnapshotControllerPermitAllTest { @Autowired @@ -68,8 +66,15 @@ public class SnapshotControllerPermitAllTest { private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private String demoUser; @@ -170,4 +175,4 @@ public void testUpdateSnapshot() throws Exception { .content(snapshotString); mockMvc.perform(request).andExpect(status().isOk()); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java index a0109e2265..9e1c4f2705 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotControllerTest.java @@ -18,11 +18,15 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.phoebus.applications.saveandrestore.model.Node; @@ -34,12 +38,8 @@ import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.phoebus.service.saveandrestore.websocket.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -57,10 +57,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") -@WebMvcTest(SnapshotController.class) public class SnapshotControllerTest { @Autowired @@ -78,8 +76,15 @@ public class SnapshotControllerTest { private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private String demoUser; @@ -344,4 +349,4 @@ public void testDeleteSnapshot4() throws Exception { verify(webSocketService, times(0)).sendMessageToClients(Mockito.any(WebSocketMessage.class)); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java index ec38a0c968..1033e9be46 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java @@ -1,4 +1,9 @@ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; @@ -7,7 +12,6 @@ import org.epics.vtype.Time; import org.epics.vtype.VFloat; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.applications.saveandrestore.model.ConfigPv; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.NodeType; @@ -19,13 +23,8 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -40,10 +39,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") -@WebMvcTest(SnapshotRestoreController.class) public class SnapshotRestorerControllerTest { @Autowired @@ -52,14 +49,21 @@ public class SnapshotRestorerControllerTest { @Autowired private String userAuthorization; - @MockBean + @Autowired private SnapshotUtil snapshotUtil; private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Test public void testRestoreFromSnapshotNode() throws Exception { @@ -155,4 +159,4 @@ public void testRestoreFromSnapshotItems() throws Exception { new TypeReference>() { }); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java index f12a840d8a..8329c683e9 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerPermitAllTest.java @@ -18,21 +18,21 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -45,18 +45,23 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") -@WebMvcTest(StructureController.class) public class StructureControllerPermitAllTest { @Autowired private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired @@ -154,4 +159,4 @@ public void testCopyNodes() throws Exception { .param("to", "target"); mockMvc.perform(request).andExpect(status().isUnauthorized()); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java index 69facdcdaa..3aae7b6772 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/StructureControllerTest.java @@ -18,25 +18,25 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; +import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.core.websocket.common.WebSocketMessage; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; -import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; -import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.phoebus.service.saveandrestore.websocket.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -53,18 +53,23 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") -@WebMvcTest(StructureController.class) public class StructureControllerTest { @Autowired private NodeDAO nodeDAO; @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired @@ -245,4 +250,4 @@ public void testCopyNodesBadRequest3() throws Exception { verify(webSocketService, times(0)).sendMessageToClients(Mockito.any(WebSocketMessage.class)); } -} +} \ No newline at end of file diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java index 170f0e7edc..cb5873319a 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerPermitAllTest.java @@ -18,10 +18,14 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.Tag; import org.phoebus.applications.saveandrestore.model.TagData; @@ -29,12 +33,8 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -48,15 +48,20 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(TagController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") public class TagControllerPermitAllTest { @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private NodeDAO nodeDAO; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java index 0007f740d9..3e0697a536 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TagControllerTest.java @@ -18,10 +18,14 @@ */ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.Tag; import org.phoebus.applications.saveandrestore.model.TagData; @@ -29,12 +33,8 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -47,15 +47,20 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) -@WebMvcTest(TagController.class) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") public class TagControllerTest { @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Autowired private NodeDAO nodeDAO; diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotControllerTest.java index 557853734d..8ae71943a9 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotControllerTest.java @@ -1,9 +1,13 @@ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.boot.test.context.SpringBootTest; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.phoebus.applications.saveandrestore.model.ConfigPv; import org.phoebus.applications.saveandrestore.model.ConfigurationData; @@ -15,11 +19,7 @@ import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -33,10 +33,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application_permit_all.properties") -@WebMvcTest(TakeSnapshotController.class) public class TakeSnapshotControllerTest { @Autowired @@ -47,8 +45,15 @@ public class TakeSnapshotControllerTest { private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + @Test public void testTakeSnapshot() throws Exception { @@ -82,4 +87,4 @@ public void testTakeSnapshotBadConfigId() throws Exception { mockMvc.perform(request).andExpect(status().isNotFound()); } -} +} \ No newline at end of file From d98fd575d4b82c1f271834025f1727c0610c9309 Mon Sep 17 00:00:00 2001 From: shroffk Date: Wed, 5 Aug 2026 10:39:37 -0400 Subject: [PATCH 07/17] use named path variables as required by springboot4 --- .../web/controllers/ComparisonController.java | 2 +- .../CompositeSnapshotController.java | 6 +++--- .../controllers/ConfigurationController.java | 2 +- .../web/controllers/HelpResource.java | 2 +- .../web/controllers/NodeController.java | 4 ++-- .../web/controllers/SnapshotController.java | 2 +- .../web/controllers/StructureController.java | 2 +- .../controllers/TakeSnapshotController.java | 4 ++-- .../web/config/WebConfigTest.java | 12 +++++------ .../web/controllers/HelpResourceTest.java | 20 +++++++++++++++++-- 10 files changed, 35 insertions(+), 21 deletions(-) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonController.java index 24edbbba3a..4f3933caeb 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonController.java @@ -61,7 +61,7 @@ public class ComparisonController extends BaseController { * comparison evaluates to "equal" for a PV. */ @GetMapping(value = "/{nodeId}", produces = JSON) - public List compare(@PathVariable String nodeId, + public List compare(@PathVariable("nodeId") String nodeId, @RequestParam(value = "tolerance", required = false, defaultValue = "0") double tolerance, @RequestParam(value = "compareMode", required = false, defaultValue = "ABSOLUTE") ComparisonMode compareMode, @RequestParam(value = "skipReadback", required = false, defaultValue = "false") boolean skipReadback) { diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java index e88e3b39ca..e7202eaaaa 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/CompositeSnapshotController.java @@ -103,7 +103,7 @@ public CompositeSnapshot updateCompositeSnapshot(@RequestBody CompositeSnapshot * @return The {@link CompositeSnapshotData} associated with the {@link CompositeSnapshot} {@link Node}. */ @GetMapping(value = "/composite-snapshot/{uniqueId}", produces = JSON) - public CompositeSnapshotData getCompositeSnapshotData(@PathVariable String uniqueId) { + public CompositeSnapshotData getCompositeSnapshotData(@PathVariable("uniqueId") String uniqueId) { return nodeDAO.getCompositeSnapshotData(uniqueId); } @@ -114,7 +114,7 @@ public CompositeSnapshotData getCompositeSnapshotData(@PathVariable String uniqu * {@link NodeType#COMPOSITE_SNAPSHOT}. */ @GetMapping(value = "/composite-snapshot/{uniqueId}/nodes", produces = JSON) - public List getCompositeSnapshotNodes(@PathVariable String uniqueId) { + public List getCompositeSnapshotNodes(@PathVariable("uniqueId") String uniqueId) { CompositeSnapshotData compositeSnapshotData = nodeDAO.getCompositeSnapshotData(uniqueId); return nodeDAO.getNodes(compositeSnapshotData.getReferencedSnapshotNodes()); } @@ -124,7 +124,7 @@ public List getCompositeSnapshotNodes(@PathVariable String uniqueId) { * @return List of {@link SnapshotItem}s contained in the referenced {@link Node}s */ @GetMapping(value = "/composite-snapshot/{uniqueId}/items", produces = JSON) - public List getCompositeSnapshotItems(@PathVariable String uniqueId) { + public List getCompositeSnapshotItems(@PathVariable("uniqueId") String uniqueId) { return nodeDAO.getSnapshotItemsFromCompositeSnapshot(uniqueId); } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java index 8194a083e7..6486a0a826 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/ConfigurationController.java @@ -91,7 +91,7 @@ public Configuration createConfiguration(@RequestParam(value = "parentNodeId") S */ @SuppressWarnings("unused") @GetMapping(value = "/{uniqueId}", produces = JSON) - public ConfigurationData getConfigurationData(@PathVariable String uniqueId) { + public ConfigurationData getConfigurationData(@PathVariable("uniqueId") String uniqueId) { return nodeDAO.getConfigurationData(uniqueId); } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java index 64359e43db..06bac5e254 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/HelpResource.java @@ -65,7 +65,7 @@ public class HelpResource { @SuppressWarnings("unused") @GetMapping(value = "{what}", produces = CONTENT_TYPE) public String getHelpContent(@RequestParam(name = "lang", required = false) String lang, - @PathVariable String what, + @PathVariable("what") String what, HttpServletRequest request) { String language = determineLang(lang, request); String content; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java index 1a83548bca..efe6f9b8a8 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/NodeController.java @@ -125,7 +125,7 @@ public List getNodes(@RequestBody List uniqueNodeIds) { */ @SuppressWarnings("unused") @GetMapping(value = "/node/{uniqueNodeId}/parent", produces = JSON) - public Node getParentNode(@PathVariable String uniqueNodeId) { + public Node getParentNode(@PathVariable("uniqueNodeId") String uniqueNodeId) { return nodeDAO.getParentNode(uniqueNodeId); } @@ -183,7 +183,7 @@ public void deleteNodes(@RequestBody List nodeIds) { @SuppressWarnings("unused") @DeleteMapping(value = "/node/{nodeId}", produces = JSON) @PreAuthorize("@authorizationHelper.mayDelete(#nodeId, #root)") - public void deleteNode(@PathVariable String nodeId) { + public void deleteNode(@PathVariable("nodeId") String nodeId) { deleteNodes(List.of(nodeId)); } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java index 6e124b9780..29ed4471c9 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotController.java @@ -57,7 +57,7 @@ public class SnapshotController extends BaseController { * @return SnapshotData object associated with #uniqueId. */ @GetMapping(value = "/snapshot/{uniqueId}", produces = JSON) - public SnapshotData getSnapshotData(@PathVariable String uniqueId) { + public SnapshotData getSnapshotData(@PathVariable("uniqueId") String uniqueId) { return nodeDAO.getSnapshotData(uniqueId); } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java index e5fb1a0c63..2f42e76944 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/StructureController.java @@ -123,7 +123,7 @@ public Node copyNodes(@RequestParam(value = "to") String to, */ @SuppressWarnings("unused") @GetMapping("/path/{uniqueNodeId}") - public String getFullPath(@PathVariable String uniqueNodeId) { + public String getFullPath(@PathVariable("uniqueNodeId") String uniqueNodeId) { String fullPath = nodeDAO.getFullPath(uniqueNodeId); if (fullPath == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND); diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotController.java index c321e57dd6..a20f523190 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TakeSnapshotController.java @@ -48,7 +48,7 @@ public class TakeSnapshotController extends BaseController { */ @SuppressWarnings("unused") @GetMapping(value = "/take-snapshot/{configNodeId}", produces = JSON) - public List takeSnapshot(@PathVariable String configNodeId) { + public List takeSnapshot(@PathVariable("configNodeId") String configNodeId) { Node configNode = nodeDAO.getNode(configNodeId); LOG.log(Level.INFO, "Take snapshot for configuration '" + configNode.getName() + "'"); ConfigurationData configurationData = nodeDAO.getConfigurationData(configNodeId); @@ -75,7 +75,7 @@ public List takeSnapshot(@PathVariable String configNodeId) { */ @SuppressWarnings("unused") @PutMapping(value = "/take-snapshot/{configNodeId}", produces = JSON) - public Snapshot takeSnapshotAndSave(@PathVariable String configNodeId, + public Snapshot takeSnapshotAndSave(@PathVariable("configNodeId") String configNodeId, @RequestParam(name = "name", required = false) String snapshotName, @RequestParam(name = "comment", required = false) String comment) { if (snapshotName != null) { diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java index 67633bc4df..f597120ee5 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/WebConfigTest.java @@ -20,17 +20,15 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.ContextHierarchy; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; -@ExtendWith(SpringExtension.class) -@ContextHierarchy({@ContextConfiguration(classes = {WebConfiguration.class, ControllersTestConfig.class, WebSecurityConfig.class})}) -@TestPropertySource(locations = "classpath:test_application.properties") +@SpringBootTest(classes = {WebConfiguration.class, ControllersTestConfig.class, WebSecurityConfig.class}, + webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@TestPropertySource(locations = "classpath:test_application.properties", + properties = "spring.main.allow-bean-definition-overriding=true") @SuppressWarnings("unused") public class WebConfigTest { diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java index a47e4e2bb5..9ae174235a 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/HelpResourceTest.java @@ -19,20 +19,36 @@ package org.phoebus.service.saveandrestore.web.controllers; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; +import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @TestPropertySource(locations = "classpath:test_application.properties") public class HelpResourceTest{ @Autowired - protected MockMvc mockMvc; + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } @Test public void testGetSearchHelp() throws Exception{ @@ -58,4 +74,4 @@ public void testGetCheatSheetUnsupportedHelpType() throws Exception{ MockHttpServletRequestBuilder request = get("/help/unsupported"); mockMvc.perform(request).andExpect(status().isNotFound()); } -} \ No newline at end of file +} From 18eefbe05878141e5d5c48e35663c2501ddf2729 Mon Sep 17 00:00:00 2001 From: shroffk Date: Wed, 5 Aug 2026 14:41:57 -0400 Subject: [PATCH 08/17] fix windows test for the Path calculations --- .../src/test/java/org/csstudio/display/builder/PathTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/display/model/src/test/java/org/csstudio/display/builder/PathTest.java b/app/display/model/src/test/java/org/csstudio/display/builder/PathTest.java index c045769227..0d88ba0352 100644 --- a/app/display/model/src/test/java/org/csstudio/display/builder/PathTest.java +++ b/app/display/model/src/test/java/org/csstudio/display/builder/PathTest.java @@ -56,6 +56,7 @@ public void testDirectory() } @Test + @DisabledOnOs(OS.WINDOWS) public void testCombineNotWindows() { String path = ModelResourceUtil.combineDisplayPaths(null, "example.opi"); From bc14788817af613fdabe6c9649a13a6c4c3a198c Mon Sep 17 00:00:00 2001 From: shroffk Date: Wed, 5 Aug 2026 14:42:50 -0400 Subject: [PATCH 09/17] Add the need compiler flag "-parameter" --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5acabadc98..28de983841 100644 --- a/pom.xml +++ b/pom.xml @@ -110,6 +110,7 @@ ${maven.compiler.target} true + -parameters -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -148,7 +149,7 @@ - + org.apache.maven.plugins maven-javadoc-plugin From 52a048408f0c3a281b86ef9dca83a382249dd238 Mon Sep 17 00:00:00 2001 From: shroffk Date: Wed, 5 Aug 2026 14:45:38 -0400 Subject: [PATCH 10/17] switch some of the tests to use real SnapshotUtil --- ...nControllerRealSnapshotUtilTestConfig.java | 16 +++++++++++++ .../controllers/ComparisonControllerTest.java | 24 +++++++++++++++---- .../SnapshotRestorerControllerTest.java | 2 +- 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ComparisonControllerRealSnapshotUtilTestConfig.java diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ComparisonControllerRealSnapshotUtilTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ComparisonControllerRealSnapshotUtilTestConfig.java new file mode 100644 index 0000000000..421dc9aa1e --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ComparisonControllerRealSnapshotUtilTestConfig.java @@ -0,0 +1,16 @@ +package org.phoebus.service.saveandrestore.web.config; + +import org.phoebus.saveandrestore.util.SnapshotUtil; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@TestConfiguration +public class ComparisonControllerRealSnapshotUtilTestConfig { + + @Bean("realSnapshotUtil") + @Primary + public SnapshotUtil snapshotUtil() { + return new SnapshotUtil(); + } +} diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java index 3e8e534a45..b43dd213d1 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java @@ -24,10 +24,12 @@ import org.phoebus.applications.saveandrestore.model.SnapshotData; import org.phoebus.applications.saveandrestore.model.SnapshotItem; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.phoebus.service.saveandrestore.web.config.ComparisonControllerRealSnapshotUtilTestConfig; import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -44,8 +46,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@SpringBootTest(classes = {ControllersTestConfig.class, WebSecurityConfig.class}, webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@SpringBootTest( + classes = {ControllersTestConfig.class, WebSecurityConfig.class}, + webEnvironment = SpringBootTest.WebEnvironment.MOCK, + properties = "spring.main.allow-bean-definition-overriding=true") @TestPropertySource(locations = "classpath:test_application.properties") +@Import(ComparisonControllerRealSnapshotUtilTestConfig.class) public class ComparisonControllerTest { @Autowired @@ -99,7 +105,8 @@ public void testSingleSnapshot() throws Exception { ConfigPv configPv2 = new ConfigPv(); configPv2.setPvName("loc://y(771.0)"); - when(nodeDAO.getParentNode("nodeId")).thenReturn(Node.builder().nodeType(NodeType.CONFIGURATION).uniqueId("configId").build()); + when(nodeDAO.getParentNode("nodeId")).thenReturn(Node.builder().nodeType(NodeType.CONFIGURATION) + .uniqueId("configId").build()); ConfigurationData configurationData = new ConfigurationData(); configurationData.setUniqueId("configId"); @@ -125,10 +132,13 @@ public void testSingleSnapshot() throws Exception { MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON)) .andReturn(); + String responseBody = result.getResponse().getContentAsString(); + List compareResults = - objectMapper.readValue(result.getResponse().getContentAsString(), + objectMapper.readValue(responseBody, new TypeReference<>() { }); + assertEquals(2, compareResults.size()); compareResults.forEach(cr -> assertTrue(cr.isEqual())); @@ -140,6 +150,7 @@ public void testSingleSnapshot() throws Exception { public void testCompositeSnapshot() throws Exception{ when(nodeDAO.getNode("nodeId")). thenReturn(Node.builder().uniqueId("nodeId").nodeType(NodeType.COMPOSITE_SNAPSHOT).build()); + CompositeSnapshotData compositeSnapshotData = new CompositeSnapshotData(); compositeSnapshotData.setReferencedSnapshotNodes(List.of("id1", "id2")); when(nodeDAO.getCompositeSnapshotData("nodeId")).thenReturn(compositeSnapshotData); @@ -184,14 +195,17 @@ public void testCompositeSnapshot() throws Exception{ MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON)) .andReturn(); + String responseBody = result.getResponse().getContentAsString(); + List compareResults = - objectMapper.readValue(result.getResponse().getContentAsString(), + objectMapper.readValue(responseBody, new TypeReference<>() { }); + assertEquals(2, compareResults.size()); compareResults.forEach(cr -> assertTrue(cr.isEqual())); reset(nodeDAO); } -} \ No newline at end of file +} diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java index 1033e9be46..6fc16f1981 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java @@ -159,4 +159,4 @@ public void testRestoreFromSnapshotItems() throws Exception { new TypeReference>() { }); } -} \ No newline at end of file +} From 7ceb150de320f45363b482b669de534314b78b18 Mon Sep 17 00:00:00 2001 From: shroffk Date: Thu, 6 Aug 2026 09:40:35 -0400 Subject: [PATCH 11/17] moving to jdk25 and javafx25 --- .github/CI_VERSIONS.md | 2 +- .github/actions/setup-java/action.yml | 2 +- pom.xml | 6 +++--- services/alarm-config-logger/pom.xml | 2 +- services/alarm-logger/pom.xml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/CI_VERSIONS.md b/.github/CI_VERSIONS.md index 5a03756022..f3b089f9b4 100644 --- a/.github/CI_VERSIONS.md +++ b/.github/CI_VERSIONS.md @@ -5,7 +5,7 @@ file is the checklist of what needs periodic review and where each pin lives. | What | Version | Defined in | Notes | |------|---------|------------|-------| -| Java (JDK) | `21` | `.github/actions/setup-java/action.yml` | Single source of truth for the CI JDK. Must match `maven.compiler.source`/`maven.compiler.target` in `pom.xml`. Review when the project adopts a new LTS. | +| Java (JDK) | `25` | `.github/actions/setup-java/action.yml` | Single source of truth for the CI JDK. Must match `maven.compiler.source`/`maven.compiler.target` in `pom.xml` and the `openjfx.version` line. Review when the project adopts a new LTS. | | Elasticsearch | `8.11.2` | `.github/workflows/_integration-test.yml` and `services/save-and-restore/docker-compose.yml` | Service container for the save-and-restore integration tests. Update both files together. | ## Automatically maintained diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml index fa997d077e..f0684ca7fb 100644 --- a/.github/actions/setup-java/action.yml +++ b/.github/actions/setup-java/action.yml @@ -7,5 +7,5 @@ runs: - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 with: distribution: temurin - java-version: '21' + java-version: '25' cache: maven diff --git a/pom.xml b/pom.xml index 28de983841..d1f5aaf1b4 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 7.0.11 1.0.8 1.0.8 - 21.0.7 + 25.0.4 26.0.1 @@ -93,8 +93,8 @@ 10.16.1.1 2.7.4b1 6.9.0.202403050737-r - 21 - 21 + 25 + 25 5.18.4 1.26.1 5.3.22 diff --git a/services/alarm-config-logger/pom.xml b/services/alarm-config-logger/pom.xml index 825f0cc74f..1440567f65 100644 --- a/services/alarm-config-logger/pom.xml +++ b/services/alarm-config-logger/pom.xml @@ -6,7 +6,7 @@ 6.0.0-SNAPSHOT - 21 + 25 4.1.0 diff --git a/services/alarm-logger/pom.xml b/services/alarm-logger/pom.xml index 8f85792902..5110c376e3 100644 --- a/services/alarm-logger/pom.xml +++ b/services/alarm-logger/pom.xml @@ -6,7 +6,7 @@ 6.0.0-SNAPSHOT - 21 + 25 4.1.0 From 16ad5dad68f4c41aca2072a4d92394fd2f451972 Mon Sep 17 00:00:00 2001 From: shroffk Date: Thu, 6 Aug 2026 14:09:33 -0400 Subject: [PATCH 12/17] upgrade springdoc version --- dependencies/phoebus-target/pom.xml | 22 +++++++++++----------- pom.xml | 1 + services/alarm-logger/pom.xml | 23 +++++++++++++++++------ services/save-and-restore/pom.xml | 2 +- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index e0abdbc8e3..465cdc7d7f 100644 --- a/dependencies/phoebus-target/pom.xml +++ b/dependencies/phoebus-target/pom.xml @@ -12,8 +12,8 @@ - release @@ -60,7 +60,7 @@ - org.phoebus @@ -481,10 +481,10 @@ - org.springdoc - springdoc-openapi-ui - 1.7.0 - + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + javax.validation @@ -500,7 +500,7 @@ - + org.slf4j slf4j-jdk14 1.7.28 @@ -551,7 +551,7 @@ epics-jackie-client 3.1.0 - + @@ -570,7 +570,7 @@ bcprov-jdk18on 1.84 - + org.apache.poi @@ -610,7 +610,7 @@ tika-core 3.2.0 - + io.fair-acc diff --git a/pom.xml b/pom.xml index 28de983841..cef83da960 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,7 @@ 1.26.1 5.3.22 2.7.18 + 3.1.0 diff --git a/services/alarm-logger/pom.xml b/services/alarm-logger/pom.xml index 8f85792902..457a882f32 100644 --- a/services/alarm-logger/pom.xml +++ b/services/alarm-logger/pom.xml @@ -62,16 +62,27 @@ kafka-clients ${kafka.version} - - org.elasticsearch.client - elasticsearch-rest-client-sniffer - ${elasticsearch.version} - co.elastic.clients elasticsearch-java ${elasticsearch.version} + + + org.apache.httpcomponents.client5 + httpclient5 + 5.2.3 + + + org.apache.httpcomponents.core5 + httpcore5 + 5.2.4 + + + org.apache.httpcomponents.core5 + httpcore5-h2 + 5.2.4 + tools.jackson.core jackson-databind @@ -144,7 +155,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.8.17 + ${springdoc.version} diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index 0928cd2d8e..1e48b038ce 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -54,7 +54,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.8.17 + ${springdoc.version} From f75352fcabdfd0bcff46c7199d1587c95dd59505 Mon Sep 17 00:00:00 2001 From: shroffk Date: Thu, 6 Aug 2026 14:17:41 -0400 Subject: [PATCH 13/17] updating the elastic client to 9.4.4 --- .../alarm/logging/AlarmMessageLogger.java | 12 +- .../alarm/logging/ElasticClientHelper.java | 283 +++++++++++------- .../logging/rest/AlarmLogSearchUtil.java | 158 ++++++++-- .../alarm/logging/rest/SearchController.java | 9 +- 4 files changed, 322 insertions(+), 140 deletions(-) diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java index 8e29dc51e5..0885c90c75 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/AlarmMessageLogger.java @@ -57,7 +57,7 @@ public class AlarmMessageLogger implements Runnable { * Create a alarm logger for the alarm messages (both state and configuration) * for a given alarm server topic. * This runnable will create the kafka streams for the given alarm messages which match the format 'topic' - * + * * @param topic - the alarm topic in kafka */ public AlarmMessageLogger(String topic) { @@ -189,9 +189,9 @@ public long extract(ConsumerRecord record, long previousTimestam alarms.split(Named.as("alarm-")) .branch((k, v) -> k.startsWith("state"), - Branched.withConsumer(alarmStateStream -> processAlarmStateStream(alarmStateStream))) + Branched.withConsumer(this::processAlarmStateStream)) .branch((k, v) -> k.startsWith("config"), - Branched.withConsumer(alarmConfigStream -> processAlarmConfigurationStream(alarmConfigStream))) + Branched.withConsumer(this::processAlarmConfigurationStream)) .defaultBranch(Branched.withConsumer(stream -> { // Log each unmatched key in the default branch stream.foreach((k, v) -> logger.warning("Unknown alarm message type for key: " + k)); @@ -314,7 +314,7 @@ public void init(ProcessorContext context) { @Override public KeyValue transform(String key, AlarmMessage value) { - + key = key.replace("\\", ""); if(value != null) { AlarmConfigMessage newValue = value.getAlarmConfigMessage(); @@ -328,9 +328,9 @@ public KeyValue transform(String key, AlarmMessage v @Override public void close() { - + } - + }; } }); diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/ElasticClientHelper.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/ElasticClientHelper.java index d6e306e0ec..b3d1b819a1 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/ElasticClientHelper.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/ElasticClientHelper.java @@ -4,24 +4,19 @@ package org.phoebus.alarm.logging; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.Refresh; -import co.elastic.clients.elasticsearch.core.BulkRequest; -import co.elastic.clients.elasticsearch.core.BulkResponse; -import co.elastic.clients.elasticsearch.indices.ExistsIndexTemplateRequest; -import co.elastic.clients.elasticsearch.indices.PutIndexTemplateRequest; -import co.elastic.clients.elasticsearch.indices.PutIndexTemplateResponse; import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; -import co.elastic.clients.transport.rest_client.RestClientTransport; -import tools.jackson.databind.ObjectMapper; -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.message.BasicHeader; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.sniff.Sniffer; +import co.elastic.clients.transport.rest5_client.Rest5ClientTransport; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; +import co.elastic.clients.transport.rest5_client.low_level.Rest5ClientBuilder; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.message.BasicHeader; import org.phoebus.applications.alarm.messages.AlarmCommandMessage; import org.phoebus.applications.alarm.messages.AlarmConfigMessage; import org.phoebus.applications.alarm.messages.AlarmStateMessage; @@ -29,6 +24,9 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.net.URISyntaxException; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Arrays; @@ -54,14 +52,12 @@ public class ElasticClientHelper { Properties props = PropertiesHelper.getProperties(); - private static RestClient restClient; + private static Rest5Client restClient; private static ElasticsearchTransport transport; private static ElasticsearchClient client; - private static AtomicReference instance = new AtomicReference<>(); - private static Sniffer sniffer; - + private static final AtomicReference instance = new AtomicReference<>(); private static final AtomicBoolean esInitialized = new AtomicBoolean(); private static final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4); @@ -73,15 +69,14 @@ public class ElasticClientHelper { BlockingQueue> commandMessagedQueue = new LinkedBlockingDeque<>(); - private final JsonMapper mapper = new JsonMapper(); - private ElasticClientHelper() { try { Runtime.getRuntime().addShutdownHook(new Thread(() -> { logger.info("Shutting down the ElasticClientHelper."); if (client != null) { try { - client.shutdown(); + // Do not call client.shutdown() with Rest5 transport because some + // client versions assume RestClientTransport internally. transport.close(); restClient.close(); } catch (IOException ex) { @@ -97,6 +92,7 @@ private ElasticClientHelper() { HttpHost[] esHttpHosts; if (esUrls.isEmpty()) { final var http_host = new HttpHost( + "http", esHost.isEmpty() ? "localhost" : esHost, esPort.isEmpty() ? 9200 : Integer.parseInt(esPort)); esHttpHosts = new HttpHost[] {http_host}; @@ -104,12 +100,20 @@ private ElasticClientHelper() { if (!esHost.isEmpty() || !esPort.isEmpty()) { logger.warning("Only one of es_urls or es_host and es_port can be specified, ignoring es_host and es_port."); } - esHttpHosts = Arrays.stream(esUrls.split(",")).map(HttpHost::create).toArray(HttpHost[]::new); + esHttpHosts = Arrays.stream(esUrls.split(",")) + .map(url -> { + try { + return HttpHost.create(url); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid URL in es_urls: " + url, e); + } + }) + .toArray(HttpHost[]::new); } final var esAuthHeader = props.getProperty("es_auth_header", ""); final var esAuthUsername = props.getProperty("es_auth_username", ""); final var esAuthPassword = props.getProperty("es_auth_password", ""); - final var restClientBuilder = RestClient.builder(esHttpHosts); + final Rest5ClientBuilder restClientBuilder = Rest5Client.builder(esHttpHosts); if (!esAuthHeader.isEmpty()) { if (!esAuthUsername.isEmpty() || !esAuthPassword.isEmpty()) { logger.warning("Only one of es_auth_header or es_auth_username and es_auth_password can be specified. Ignoring es_auth_username and es_auth_password."); @@ -118,19 +122,20 @@ private ElasticClientHelper() { new Header[] {new BasicHeader("Authorization", esAuthHeader)}); } else if (!esAuthUsername.isEmpty() || !esAuthPassword.isEmpty()) { final var credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(esAuthUsername, esAuthPassword)); + credentialsProvider.setCredentials( + new AuthScope(esHttpHosts[0]), + new UsernamePasswordCredentials(esAuthUsername, esAuthPassword.toCharArray())); restClientBuilder.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); } restClient = restClientBuilder.build(); - transport = new RestClientTransport( + transport = new Rest5ClientTransport( restClient, - new Jackson3JsonpMapper(mapper) + new Jackson3JsonpMapper(new JsonMapper()) ); client = new ElasticsearchClient(transport); if (props.getProperty("es_sniff").equals("true")) { - sniffer = Sniffer.builder(restClient).build(); - logger.log(Level.INFO, "ES Sniff feature is enabled"); + logger.log(Level.WARNING, "es_sniff=true is ignored because Rest5Client does not support the legacy sniffer API."); } // Initialize the elastic templates esInitialized.set(!Boolean.parseBoolean(props.getProperty("es_create_templates"))); @@ -140,11 +145,15 @@ private ElasticClientHelper() { 0, 250, TimeUnit.MILLISECONDS); } catch (Exception e) { try { - job.cancel(false); - sniffer.close(); - transport.close(); - restClient.close(); - client.shutdown(); + if (job != null) { + job.cancel(false); + } + if (transport != null) { + transport.close(); + } + if (restClient != null) { + restClient.close(); + } } catch (IOException ex) { logger.log(Level.WARNING, "Failed to close the elastic client", ex); } @@ -178,6 +187,10 @@ public ElasticsearchClient getClient() { return client; } + public Rest5Client getRestClient() { + return restClient; + } + /** * Index an alarm state message * @@ -246,42 +259,89 @@ public void run() { logger.log(Level.SEVERE, "failed to create the alarm log indices ", e); } } - if (stateMessagedQueue.size() + configMessagedQueue.size() > 0) { - logger.log(Level.INFO, "batch execution of : " + stateMessagedQueue.size() + " state messages and " + configMessagedQueue.size() + " config messages"); - BulkRequest.Builder bulkRequest = new BulkRequest.Builder().refresh(Refresh.True); + int stateSize = stateMessagedQueue.size(); + int configSize = configMessagedQueue.size(); + int commandSize = commandMessagedQueue.size(); + if (stateSize + configSize + commandSize > 0) { + logger.log(Level.INFO, "batch execution of : " + stateSize + " state, " + configSize + " config, " + commandSize + " command messages"); Collection> statePairs = new ArrayList<>(); stateMessagedQueue.drainTo(statePairs); Collection> configPairs = new ArrayList<>(); configMessagedQueue.drainTo(configPairs); Collection> commandPairs = new ArrayList<>(); commandMessagedQueue.drainTo(commandPairs); - statePairs.forEach(pair -> bulkRequest.operations(op -> op - .index(idx -> idx - .index(pair.getKey().toLowerCase()) - .document(pair.getValue().sourceMap())))); - configPairs.forEach(pair -> bulkRequest.operations(op -> op - .index(idx -> idx - .index(pair.getKey().toLowerCase()) - .document(pair.getValue().sourceMap())))); - commandPairs.forEach(pair -> bulkRequest.operations(op -> op - .index(idx -> idx - .index(pair.getKey().toLowerCase()) - .document(pair.getValue().sourceMap())))); try { - BulkResponse bulkResponse = client.bulk(bulkRequest.build()); - bulkResponse.items().forEach(item -> { - if (item.error() != null) { - logger.log(Level.SEVERE, "Failed while indexing to " + item.index() + " type " - + item.operationType() + item.error().reason() + "]"); - } - } - ); + performBulkIndex(statePairs, configPairs, commandPairs); } catch (IOException e) { logger.log(Level.SEVERE, "failed to log messages to index ", e); + } catch (Exception e) { + logger.log(Level.SEVERE, "Unexpected error during bulk indexing: " + e.getClass().getName() + " - " + e.getMessage(), e); } } } + /** + * Perform bulk indexing using low-level Rest5 API to avoid media-type header issues + * with elasticsearch-java 9.x high-level client against ES 8.x backends. + */ + private void performBulkIndex(Collection> statePairs, + Collection> configPairs, + Collection> commandPairs) + throws IOException { + if (statePairs.isEmpty() && configPairs.isEmpty() && commandPairs.isEmpty()) { + return; + } + + StringBuilder bulkPayload = new StringBuilder(); + long successCount = 0L; + + // Add state pairs + for (var pair : statePairs) { + String indexName = pair.getKey().toLowerCase(); + bulkPayload.append("{\"index\":{\"_index\":\"").append(indexName).append("\"}}\n"); + String jsonDoc = ElasticClientHelper.toJson(pair.getValue().sourceMap()); + bulkPayload.append(jsonDoc).append("\n"); + successCount++; + } + + // Add config pairs + for (var pair : configPairs) { + String indexName = pair.getKey().toLowerCase(); + bulkPayload.append("{\"index\":{\"_index\":\"").append(indexName).append("\"}}\n"); + String jsonDoc = ElasticClientHelper.toJson(pair.getValue().sourceMap()); + bulkPayload.append(jsonDoc).append("\n"); + successCount++; + } + + // Add command pairs + for (var pair : commandPairs) { + String indexName = pair.getKey().toLowerCase(); + bulkPayload.append("{\"index\":{\"_index\":\"").append(indexName).append("\"}}\n"); + String jsonDoc = ElasticClientHelper.toJson(pair.getValue().sourceMap()); + bulkPayload.append(jsonDoc).append("\n"); + successCount++; + } + + if (bulkPayload.isEmpty()) { + return; + } + + Request request = new Request("POST", "/_bulk"); + request.addParameter("refresh", "true"); + request.setJsonEntity(bulkPayload.toString()); + + try { + int statusCode = restClient.performRequest(request).getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + logger.log(Level.INFO, "Bulk indexing completed successfully: " + successCount + " items indexed"); + } else { + logger.log(Level.WARNING, "Bulk indexing returned HTTP " + statusCode + " but " + successCount + " items were sent"); + } + } catch (ResponseException e) { + logger.log(Level.SEVERE, "Bulk indexing failed with HTTP " + e.getResponse().getStatusCode(), e); + } + } + private static final Properties props = new Properties(); static { @@ -303,66 +363,63 @@ public void run() { * @throws IOException if Elasticsearch interaction fails */ public void initializeIndices() throws IOException { - // Create the alarm state messages index template - boolean exists = client.indices().existsIndexTemplate(ExistsIndexTemplateRequest.of(i -> i.name(ALARM_STATE_TEMPLATE))).value(); - - if (!exists) { - try (InputStream is = ElasticClientHelper.class.getResourceAsStream("/alarms_state_template.json")) { - PutIndexTemplateRequest templateRequest = new PutIndexTemplateRequest.Builder() - .name(ALARM_STATE_TEMPLATE) - .indexPatterns(Arrays.asList(ALARM_STATE_TEMPLATE_PATTERN)) - .withJson(is) - .priority(1L) - .create(true) - .build(); - PutIndexTemplateResponse putTemplateResponse = client.indices().putIndexTemplate(templateRequest); - putTemplateResponse.acknowledged(); - logger.log(Level.INFO, "Created " + ALARM_STATE_TEMPLATE + " template."); - } catch (Exception e) { - logger.log(Level.INFO, "Failed to create template " + ALARM_STATE_TEMPLATE + " template.", e); - } - } + createTemplateIfMissing(ALARM_STATE_TEMPLATE, ALARM_STATE_TEMPLATE_PATTERN, "/alarms_state_template.json", 1L); + createTemplateIfMissing(ALARM_CMD_TEMPLATE, ALARM_CMD_TEMPLATE_PATTERN, "/alarms_cmd_template.json", 2L); + createTemplateIfMissing(ALARM_CONFIG_TEMPLATE, ALARM_CONFIG_TEMPLATE_PATTERN, "/alarms_config_template.json", 3L); + } - // Create the alarm command messages index template - exists = client.indices().existsIndexTemplate(ExistsIndexTemplateRequest.of(i -> i.name(ALARM_CMD_TEMPLATE))).value(); - - if (!exists) { - try (InputStream is = ElasticClientHelper.class.getResourceAsStream("/alarms_cmd_template.json")) { - PutIndexTemplateRequest templateRequest = new PutIndexTemplateRequest.Builder() - .name(ALARM_CMD_TEMPLATE) - .indexPatterns(Arrays.asList(ALARM_CMD_TEMPLATE_PATTERN)) - .withJson(is) - .priority(2L) - .create(true) - .build(); - PutIndexTemplateResponse putTemplateResponse = client.indices().putIndexTemplate(templateRequest); - putTemplateResponse.acknowledged(); - logger.log(Level.INFO, "Created " + ALARM_CMD_TEMPLATE + " template."); - } catch (Exception e) { - logger.log(Level.INFO, "Failed to create template " + ALARM_CMD_TEMPLATE + " template.", e); + private void createTemplateIfMissing(String templateName, + String pattern, + String resource, + long priority) { + try (InputStream is = ElasticClientHelper.class.getResourceAsStream(resource)) { + if (is == null) { + throw new IOException("Template resource not found: " + resource); } - } - - // Create the alarm config messages index template - exists = client.indices().existsIndexTemplate(ExistsIndexTemplateRequest.of(i -> i.name(ALARM_CONFIG_TEMPLATE))).value(); - - if (!exists) { - try (InputStream is = ElasticClientHelper.class.getResourceAsStream("/alarms_config_template.json")) { - PutIndexTemplateRequest templateRequest = new PutIndexTemplateRequest.Builder() - .name(ALARM_CONFIG_TEMPLATE) - .indexPatterns(Arrays.asList(ALARM_CONFIG_TEMPLATE_PATTERN)) - .withJson(is) - .priority(3L) - .create(true) - .build(); - PutIndexTemplateResponse putTemplateResponse = client.indices().putIndexTemplate(templateRequest); - putTemplateResponse.acknowledged(); - logger.log(Level.INFO, "Created " + ALARM_CONFIG_TEMPLATE + " template."); - } catch (Exception e) { - logger.log(Level.INFO, "Failed to create template " + ALARM_CONFIG_TEMPLATE + " template.", e); + final String templateJson = new String(is.readAllBytes(), StandardCharsets.UTF_8).trim(); + final String payload = toComposableTemplatePayload(templateJson, pattern, priority); + final String endpoint = "/_index_template/" + URLEncoder.encode(templateName, StandardCharsets.UTF_8); + + Request request = new Request("PUT", endpoint); + request.addParameter("create", "true"); + request.setJsonEntity(payload); + + int statusCode = restClient.performRequest(request).getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + logger.log(Level.INFO, "Created " + templateName + " template."); + } else { + logger.log(Level.WARNING, "Template creation returned HTTP " + statusCode + " for " + templateName + "."); + } + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 409) { + logger.log(Level.FINE, "Template " + templateName + " already exists."); + return; } + logger.log(Level.INFO, "Failed to create template " + templateName + " template.", e); + } catch (Exception e) { + logger.log(Level.INFO, "Failed to create template " + templateName + " template.", e); } } + + /** + * Builds a composable template payload by combining existing template JSON + * content with runtime index pattern and priority. + */ + private String toComposableTemplatePayload(String templateJson, String pattern, long priority) throws IOException { + String trimmed = templateJson.trim(); + if (!trimmed.startsWith("{")) { + throw new IOException("Invalid template JSON content."); + } + String escapedPattern = pattern.replace("\\", "\\\\").replace("\"", "\\\""); + return "{\"index_patterns\":[\"" + escapedPattern + "\"],\"priority\":" + priority + "," + trimmed.substring(1); + } + } + + /** + * Serialize a map to JSON string using the mapper. + */ + static String toJson(java.util.Map map) { + return new tools.jackson.databind.json.JsonMapper().writeValueAsString(map); } } diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java index a3393cebfe..80a3427bd6 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java @@ -10,7 +10,8 @@ import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery; import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery; import co.elastic.clients.elasticsearch.core.SearchRequest; -import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; @@ -70,10 +71,12 @@ public class AlarmLogSearchUtil { * Find all the log (state and config) messages which match the search criteria * * @param client elastic client + * @param restClient low-level Rest5Client for performing requests * @param searchParameters search parameters * @return list of alarm state and config messages */ public static List search(ElasticsearchClient client, + Rest5Client restClient, Map searchParameters) { logger.fine("searching for alarm log entires : " + searchParameters.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue()).collect(Collectors.joining())); @@ -276,16 +279,53 @@ public static List search(ElasticsearchClient client, } SearchRequest searchRequest = searchRequestBuilder.build(); try { - SearchResponse strResponse = client.search(searchRequest, JsonNode.class); - return strResponse.hits().hits().stream().map(hit -> { - JsonNode jsonNode = hit.source(); + // Build the search request body as JSON for low-level API + String requestBody = buildSearchJson(searchRequest); + + // Determine the target indices + String indexParam = indexList.isEmpty() ? "" : String.join(",", indexList); + String endpoint = indexParam.isEmpty() ? "/_search" : "/" + indexParam + "/_search"; + + logger.fine("Search endpoint: " + endpoint); + logger.fine("Search body: " + requestBody); + + // Execute search via low-level API (use POST for requests with body) + Request request = new Request("POST", endpoint); + request.setJsonEntity(requestBody); + var response = restClient.performRequest(request); + + if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { + // Parse the response + JsonNode responseJson = mapper.readTree(response.getEntity().getContent()); + JsonNode hits = responseJson.get("hits").get("hits"); + + List results = new ArrayList<>(); + if (hits.isArray()) { + for (JsonNode hit : hits) { + JsonNode source = hit.get("_source"); + if (source != null) { + try { + results.add(mapper.treeToValue(source, AlarmLogMessage.class)); + } catch (JacksonException e) { + logger.log(Level.SEVERE, "Failed to parse the searched alarm log messages. " + source, e); + } + } + } + } + return results; + } else { + // Log the error response body + String errorBody = ""; try { - return mapper.treeToValue(jsonNode, AlarmLogMessage.class); - } catch (JacksonException e) { - logger.log(Level.SEVERE, "Failed to parse the searched alarm log messages. " + hit, e); + errorBody = new String(response.getEntity().getContent().readAllBytes()); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not read error response body", e); } - return null; - }).collect(Collectors.toList()); + logger.log(Level.SEVERE, "Search failed with status code: " + response.getStatusCode() + + "\nEndpoint: " + endpoint + + "\nRequest body: " + requestBody + + "\nError response: " + errorBody); + } } catch (IOException e) { logger.log(Level.SEVERE, "Failed to search for alarm logs ", e); } @@ -296,10 +336,13 @@ public static List search(ElasticsearchClient client, * Return the latest alarm config message associated with 'config' * * @param client elastic client + * @param restClient low-level Rest5Client for performing requests * @param allRequestParams the wildcard pattern which matches the 'config' * @return last alarm config message for the given 'config' */ - public static List searchConfig(ElasticsearchClient client, Map allRequestParams) { + public static List searchConfig(ElasticsearchClient client, + Rest5Client restClient, + Map allRequestParams) { String configString = allRequestParams.get("config"); // Determine which alarm config to specify as Elasticsearch index, convert to lower case as // indices are created using lower case. @@ -323,16 +366,50 @@ public static List searchConfig(ElasticsearchClient client, Map ); try { - SearchResponse strResponse = client.search(searchRequest, JsonNode.class); - return strResponse.hits().hits().stream().map(hit -> { - JsonNode jsonNode = hit.source(); + // Build the search request body as JSON for low-level API + String requestBody = buildSearchJson(searchRequest); + String endpoint = "/" + alarmConfig + "_alarms_config_*/_search"; + + logger.fine("Search config endpoint: " + endpoint); + logger.fine("Search config body: " + requestBody); + + // Execute search via low-level API (use POST for requests with body) + Request request = new Request("POST", endpoint); + request.setJsonEntity(requestBody); + var response = restClient.performRequest(request); + + if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { + // Parse the response + JsonNode responseJson = mapper.readTree(response.getEntity().getContent()); + JsonNode hits = responseJson.get("hits").get("hits"); + + List results = new ArrayList<>(); + if (hits.isArray()) { + for (JsonNode hit : hits) { + JsonNode source = hit.get("_source"); + if (source != null) { + try { + results.add(mapper.treeToValue(source, AlarmLogMessage.class)); + } catch (JacksonException e) { + logger.log(Level.SEVERE, "Failed to parse the searched alarm config messages. " + source, e); + } + } + } + } + return results; + } else { + // Log the error response body + String errorBody = ""; try { - return mapper.treeToValue(jsonNode, AlarmLogMessage.class); - } catch (JacksonException e) { - logger.log(Level.SEVERE, "Failed to parse the searched alarm config messages. " + hit, e); + errorBody = new String(response.getEntity().getContent().readAllBytes()); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not read error response body", e); } - return null; - }).collect(Collectors.toList()); + logger.log(Level.SEVERE, "Search config failed with status code: " + response.getStatusCode() + + "\nEndpoint: " + endpoint + + "\nRequest body: " + requestBody + + "\nError response: " + errorBody); + } } catch (IOException e) { logger.log(Level.SEVERE, "Failed to search for alarm config logs ", e); } @@ -394,4 +471,49 @@ public static int getDateSpanInDays(String indexDateSpanUnits){ return -1; } } + + /** + * Helper method to convert SearchRequest to JSON string for low-level API + * This works by using Jackson to serialize the SearchRequest object tree to JSON + * NOTE: We exclude 'index' from the JSON since indices are specified in the URL path + */ + private static String buildSearchJson(SearchRequest searchRequest) throws IOException { + // Serialize the entire request first to get all fields + String fullJson = mapper.writeValueAsString(searchRequest); + JsonNode fullNode = mapper.readTree(fullJson); + + // Build a new JSON object with only the fields Elasticsearch expects in the body + Map searchBody = new java.util.LinkedHashMap<>(); + + // Query - only include if present + if (fullNode.has("query") && !fullNode.get("query").isNull()) { + searchBody.put("query", mapper.convertValue(fullNode.get("query"), Object.class)); + } + + // Size - only include if present + if (fullNode.has("size") && !fullNode.get("size").isNull()) { + searchBody.put("size", fullNode.get("size").asInt()); + } + + // Sort - only include if present + if (fullNode.has("sort") && !fullNode.get("sort").isNull() && fullNode.get("sort").isArray()) { + searchBody.put("sort", mapper.convertValue(fullNode.get("sort"), Object.class)); + } + + String result = mapper.writeValueAsString(searchBody); + logger.fine("Built search JSON: " + result); + return result; + } + + private static Object toMap(Query query) throws IOException { + // Serialize the Query object to a map via JSON round-trip + String json = mapper.writeValueAsString(query); + return mapper.readValue(json, Object.class); + } + + private static List toList(java.util.List list) throws IOException { + // Serialize the list to map via JSON round-trip + String json = mapper.writeValueAsString(list); + return mapper.readValue(json, List.class); + } } diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java index 33141d0048..ebde1f3efe 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java @@ -98,7 +98,8 @@ public String info() { }) @RequestMapping(value = "/search/alarm", method = RequestMethod.GET) public List search(@Parameter(hidden = true) @RequestParam Map allRequestParams) { - List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), allRequestParams); + List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), + ElasticClientHelper.getInstance().getRestClient(), allRequestParams); return result; } @@ -107,7 +108,8 @@ public List search(@Parameter(hidden = true) @RequestParam Map< public List searchPv(@Parameter(name="pv", description = "PV name") @PathVariable String pv) { Map searchParameters = new HashMap<>(); searchParameters.put("pv", pv); - List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), searchParameters); + List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), + ElasticClientHelper.getInstance().getRestClient(), searchParameters); return result; } @@ -124,7 +126,8 @@ public List searchConfig(@Parameter(hidden = true) @RequestPara allRequestParams.get("config").isEmpty()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST); } - List result = AlarmLogSearchUtil.searchConfig(ElasticClientHelper.getInstance().getClient(), allRequestParams); + List result = AlarmLogSearchUtil.searchConfig(ElasticClientHelper.getInstance().getClient(), + ElasticClientHelper.getInstance().getRestClient(), allRequestParams); return result; } From 413818b8d055dc614e4f9876f7c94b9fd3d3e1d1 Mon Sep 17 00:00:00 2001 From: shroffk Date: Thu, 6 Aug 2026 14:51:49 -0400 Subject: [PATCH 14/17] consistent switch to low level elastic rest client --- .../logging/rest/AlarmLogSearchUtil.java | 159 +++++++-------- .../persistence/config/ElasticConfig.java | 192 +++++++++--------- 2 files changed, 171 insertions(+), 180 deletions(-) diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java index 80a3427bd6..e16a3466e8 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java @@ -10,6 +10,7 @@ import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery; import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery; import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import tools.jackson.core.JacksonException; @@ -279,53 +280,10 @@ public static List search(ElasticsearchClient client, } SearchRequest searchRequest = searchRequestBuilder.build(); try { - // Build the search request body as JSON for low-level API String requestBody = buildSearchJson(searchRequest); - - // Determine the target indices String indexParam = indexList.isEmpty() ? "" : String.join(",", indexList); String endpoint = indexParam.isEmpty() ? "/_search" : "/" + indexParam + "/_search"; - - logger.fine("Search endpoint: " + endpoint); - logger.fine("Search body: " + requestBody); - - // Execute search via low-level API (use POST for requests with body) - Request request = new Request("POST", endpoint); - request.setJsonEntity(requestBody); - var response = restClient.performRequest(request); - - if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { - // Parse the response - JsonNode responseJson = mapper.readTree(response.getEntity().getContent()); - JsonNode hits = responseJson.get("hits").get("hits"); - - List results = new ArrayList<>(); - if (hits.isArray()) { - for (JsonNode hit : hits) { - JsonNode source = hit.get("_source"); - if (source != null) { - try { - results.add(mapper.treeToValue(source, AlarmLogMessage.class)); - } catch (JacksonException e) { - logger.log(Level.SEVERE, "Failed to parse the searched alarm log messages. " + source, e); - } - } - } - } - return results; - } else { - // Log the error response body - String errorBody = ""; - try { - errorBody = new String(response.getEntity().getContent().readAllBytes()); - } catch (Exception e) { - logger.log(Level.WARNING, "Could not read error response body", e); - } - logger.log(Level.SEVERE, "Search failed with status code: " + response.getStatusCode() + - "\nEndpoint: " + endpoint + - "\nRequest body: " + requestBody + - "\nError response: " + errorBody); - } + return executeSearch(restClient, endpoint, requestBody, "alarm logs"); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to search for alarm logs ", e); } @@ -366,50 +324,9 @@ public static List searchConfig(ElasticsearchClient client, ); try { - // Build the search request body as JSON for low-level API String requestBody = buildSearchJson(searchRequest); String endpoint = "/" + alarmConfig + "_alarms_config_*/_search"; - - logger.fine("Search config endpoint: " + endpoint); - logger.fine("Search config body: " + requestBody); - - // Execute search via low-level API (use POST for requests with body) - Request request = new Request("POST", endpoint); - request.setJsonEntity(requestBody); - var response = restClient.performRequest(request); - - if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) { - // Parse the response - JsonNode responseJson = mapper.readTree(response.getEntity().getContent()); - JsonNode hits = responseJson.get("hits").get("hits"); - - List results = new ArrayList<>(); - if (hits.isArray()) { - for (JsonNode hit : hits) { - JsonNode source = hit.get("_source"); - if (source != null) { - try { - results.add(mapper.treeToValue(source, AlarmLogMessage.class)); - } catch (JacksonException e) { - logger.log(Level.SEVERE, "Failed to parse the searched alarm config messages. " + source, e); - } - } - } - } - return results; - } else { - // Log the error response body - String errorBody = ""; - try { - errorBody = new String(response.getEntity().getContent().readAllBytes()); - } catch (Exception e) { - logger.log(Level.WARNING, "Could not read error response body", e); - } - logger.log(Level.SEVERE, "Search config failed with status code: " + response.getStatusCode() + - "\nEndpoint: " + endpoint + - "\nRequest body: " + requestBody + - "\nError response: " + errorBody); - } + return executeSearch(restClient, endpoint, requestBody, "alarm config logs"); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to search for alarm config logs ", e); } @@ -516,4 +433,74 @@ private static List toList(java.util.List list) throws IOException { String json = mapper.writeValueAsString(list); return mapper.readValue(json, List.class); } + + private static List executeSearch(Rest5Client restClient, + String endpoint, + String requestBody, + String context) throws IOException { + logger.fine("Search endpoint: " + endpoint); + logger.fine("Search body: " + requestBody); + + Request request = new Request("POST", endpoint); + request.setJsonEntity(requestBody); + + try { + var response = restClient.performRequest(request); + return parseHits(response, context); + } catch (ResponseException e) { + String errorBody = readExceptionBody(e); + logger.log(Level.SEVERE, + "Failed to search " + context + " (HTTP " + e.getResponse().getStatusCode() + ")" + + "\nEndpoint: " + endpoint + + "\nRequest body: " + requestBody + + "\nError response: " + errorBody, + e); + return Collections.emptyList(); + } + } + + private static List parseHits(co.elastic.clients.transport.rest5_client.low_level.Response response, + String context) throws IOException { + int statusCode = response.getStatusCode(); + if (statusCode < 200 || statusCode >= 300) { + String errorBody = ""; + if (response.getEntity() != null && response.getEntity().getContent() != null) { + errorBody = new String(response.getEntity().getContent().readAllBytes()); + } + logger.log(Level.SEVERE, + "Failed to search " + context + " (HTTP " + statusCode + ")" + + "\nError response: " + errorBody); + return Collections.emptyList(); + } + + JsonNode responseJson = mapper.readTree(response.getEntity().getContent()); + JsonNode hits = responseJson.path("hits").path("hits"); + if (!hits.isArray()) { + return Collections.emptyList(); + } + + List results = new ArrayList<>(); + for (JsonNode hit : hits) { + JsonNode source = hit.get("_source"); + if (source != null) { + try { + results.add(mapper.treeToValue(source, AlarmLogMessage.class)); + } catch (JacksonException e) { + logger.log(Level.SEVERE, "Failed to parse searched " + context + " entry: " + source, e); + } + } + } + return results; + } + + private static String readExceptionBody(ResponseException e) { + try { + if (e.getResponse().getEntity() != null && e.getResponse().getEntity().getContent() != null) { + return new String(e.getResponse().getEntity().getContent().readAllBytes()); + } + } catch (Exception ignored) { + // Best-effort logging helper. + } + return ""; + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java index 85ac482b46..d83fd48703 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java @@ -1,19 +1,14 @@ package org.phoebus.service.saveandrestore.persistence.config; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.Refresh; -import co.elastic.clients.elasticsearch._types.Result; -import co.elastic.clients.elasticsearch.core.IndexRequest; -import co.elastic.clients.elasticsearch.core.IndexResponse; -import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; -import co.elastic.clients.elasticsearch.indices.CreateIndexResponse; -import co.elastic.clients.elasticsearch.indices.ExistsRequest; import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; -import co.elastic.clients.transport.endpoints.BooleanResponse; import co.elastic.clients.transport.rest5_client.Rest5ClientTransport; +import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import co.elastic.clients.transport.rest5_client.low_level.Rest5ClientBuilder; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.module.SimpleModule; import org.apache.hc.client5.http.auth.AuthScope; @@ -37,6 +32,8 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.net.URLEncoder; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; @@ -95,6 +92,8 @@ public class ElasticConfig { private ElasticsearchClient client; + private Rest5Client restClient; + private ObjectMapper objectMapper; private static final Node ROOT_NODE; @@ -138,19 +137,19 @@ public ElasticsearchClient getClient() { JsonMapper jsonMapper = JsonMapper.builder() .addModule(module) .build(); + objectMapper = jsonMapper; Jackson3JsonpMapper jackson3JsonpMapper = new Jackson3JsonpMapper(jsonMapper); ElasticsearchTransport transport = new Rest5ClientTransport( httpClient, jackson3JsonpMapper ); + restClient = httpClient; client = new ElasticsearchClient(transport); - // Each ElasticConfig bean (i.e. each Spring context) ensures its own indices and - // root node exist. Both helpers are idempotent (they check existence before - // creating), so this is safe to run whenever a new client is built — in particular - // it lets integration tests with isolated, per-class indices each get their own root. - elasticIndexValidation(client); - elasticIndexInitialization(client); + // Use low-level requests for index/bootstrap operations to keep behavior stable + // with ES 8 backends while staying on elasticsearch-java 9 + Jackson 3. + elasticIndexValidation(); + elasticIndexInitialization(); } return client; } @@ -158,108 +157,113 @@ public ElasticsearchClient getClient() { /** * Create the indices and templates if they don't exist * - * @param client */ - void elasticIndexValidation(ElasticsearchClient client) { - - // Tree index - try (InputStream is = ElasticConfig.class.getResourceAsStream("/tree_node_mapping.json")) { - BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_TREE_INDEX))); - if (!exits.value()) { - CreateIndexResponse result = client.indices().create( - CreateIndexRequest.of( - c -> c.index(ES_TREE_INDEX).withJson(is))); - logger.info("Created index: " + ES_TREE_INDEX + " : acknowledged " + result.acknowledged()); - } - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to create index " + ES_TREE_INDEX, e); - } + void elasticIndexValidation() { + ensureIndex(ES_TREE_INDEX, "/tree_node_mapping.json"); + ensureIndex(ES_CONFIGURATION_INDEX, "/configuration_mapping.json"); + ensureIndex(ES_SNAPSHOT_INDEX, "/snapshot_mapping.json"); + ensureIndex(ES_COMPOSITE_SNAPSHOT_INDEX, "/composite_snapshot_mapping.json"); + ensureIndex(ES_FILTER_INDEX, "/filter_mapping.json"); + } - // Configuration index - try (InputStream is = ElasticConfig.class.getResourceAsStream("/configuration_mapping.json")) { - BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_CONFIGURATION_INDEX))); - if (!exits.value()) { - CreateIndexResponse result = client.indices().create( - CreateIndexRequest.of( - c -> c.index(ES_CONFIGURATION_INDEX).withJson(is))); - logger.info("Created index: " + ES_CONFIGURATION_INDEX + " : acknowledged " + result.acknowledged()); + /** + * Create root node if it does not exist + * + */ + private void elasticIndexInitialization() { + + try { + if (!documentExists(ES_TREE_INDEX, ROOT_FOLDER_UNIQUE_ID)) { + ESTreeNode elasticsearchTreeNode = new ESTreeNode(); + elasticsearchTreeNode.setNode(ROOT_NODE); + + Request request = new Request("PUT", "/" + encodePathSegment(ES_TREE_INDEX) + + "/_doc/" + encodePathSegment(ROOT_FOLDER_UNIQUE_ID)); + request.addParameter("refresh", "true"); + request.setJsonEntity(objectMapper.writeValueAsString(elasticsearchTreeNode)); + + int statusCode = restClient.performRequest(request).getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + logger.info("Created root node in index '" + ES_TREE_INDEX + "'."); + } else { + logger.warning("Failed to create root node in index '" + ES_TREE_INDEX + "' (HTTP " + statusCode + ")."); + } + } else { + logger.info("Root node already exists in index '" + ES_TREE_INDEX + "'."); } } catch (IOException e) { - logger.log(Level.WARNING, "Failed to create index " + ES_CONFIGURATION_INDEX, e); + logger.log(Level.WARNING, "Failed to initialize root node in index '" + ES_TREE_INDEX + "'.", e); } + } - // SnapshotData index - try (InputStream is = ElasticConfig.class.getResourceAsStream("/snapshot_mapping.json")) { - BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_SNAPSHOT_INDEX))); - if (!exits.value()) { - CreateIndexResponse result = client.indices().create( - CreateIndexRequest.of( - c -> c.index(ES_SNAPSHOT_INDEX).withJson(is))); - logger.info("Created index: " + ES_SNAPSHOT_INDEX + " : acknowledged " + result.acknowledged()); + private void ensureIndex(String indexName, String mappingResource) { + try { + if (indexExists(indexName)) { + return; } - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to create index " + ES_SNAPSHOT_INDEX, e); - } - // Composite snapshot index - try (InputStream is = ElasticConfig.class.getResourceAsStream("/composite_snapshot_mapping.json")) { - BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_COMPOSITE_SNAPSHOT_INDEX))); - if (!exits.value()) { - CreateIndexResponse result = client.indices().create( - CreateIndexRequest.of( - c -> c.index(ES_COMPOSITE_SNAPSHOT_INDEX).withJson(is))); - logger.info("Created index: " + ES_COMPOSITE_SNAPSHOT_INDEX + " : acknowledged " + result.acknowledged()); + String mapping = readResource(mappingResource); + Request request = new Request("PUT", "/" + encodePathSegment(indexName)); + request.setJsonEntity(mapping); + int statusCode = restClient.performRequest(request).getStatusCode(); + + if (statusCode >= 200 && statusCode < 300) { + logger.info("Created index '" + indexName + "'."); + } else { + logger.warning("Failed to create index '" + indexName + "' (HTTP " + statusCode + ")."); + } + } catch (ResponseException e) { + int statusCode = e.getResponse().getStatusCode(); + if (statusCode == 400 && e.getMessage().contains("resource_already_exists_exception")) { + logger.info("Index '" + indexName + "' already exists."); + return; } + logger.log(Level.WARNING, "Failed to create index '" + indexName + "' (HTTP " + statusCode + ").", e); } catch (IOException e) { - logger.log(Level.WARNING, "Failed to create index " + ES_COMPOSITE_SNAPSHOT_INDEX, e); + logger.log(Level.WARNING, "Failed to create index '" + indexName + "'.", e); } + } - // Filter index - try (InputStream is = ElasticConfig.class.getResourceAsStream("/filter_mapping.json")) { - BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(ES_FILTER_INDEX))); - if (!exits.value()) { - CreateIndexResponse result = client.indices().create( - CreateIndexRequest.of( - c -> c.index(ES_FILTER_INDEX).withJson(is))); - logger.info("Created index: " + ES_FILTER_INDEX + " : acknowledged " + result.acknowledged()); + private boolean indexExists(String indexName) throws IOException { + try { + Request request = new Request("HEAD", "/" + encodePathSegment(indexName)); + int statusCode = restClient.performRequest(request).getStatusCode(); + return statusCode >= 200 && statusCode < 300; + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return false; } - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to create index " + ES_FILTER_INDEX, e); + throw e; } } - /** - * Create root node if it does not exist - * - * @param indexClient the elastic client instance used to create the default resources - */ - private void elasticIndexInitialization(ElasticsearchClient indexClient) { - + private boolean documentExists(String indexName, String documentId) throws IOException { try { - if (!indexClient.exists(e -> e.index(ES_TREE_INDEX).id(ROOT_FOLDER_UNIQUE_ID)).value()) { - Date now = new Date(); - ESTreeNode elasticsearchTreeNode = new ESTreeNode(); - elasticsearchTreeNode.setNode(ROOT_NODE); - - IndexRequest indexRequest = - IndexRequest.of(i -> - i.index(ES_TREE_INDEX) - .id(ROOT_FOLDER_UNIQUE_ID) - .document(elasticsearchTreeNode) - .refresh(Refresh.True)); - IndexResponse response = client.index(indexRequest); + Request request = new Request("HEAD", "/" + encodePathSegment(indexName) + + "/_doc/" + encodePathSegment(documentId)); + int statusCode = restClient.performRequest(request).getStatusCode(); + return statusCode >= 200 && statusCode < 300; + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return false; + } + throw e; + } + } - if (response.result().equals(Result.Created)) { - logger.log(Level.INFO, "Root node created"); - } - } else { - logger.log(Level.INFO, "Root node found, not creating it"); + private static String readResource(String resourcePath) throws IOException { + try (InputStream is = ElasticConfig.class.getResourceAsStream(resourcePath)) { + if (is == null) { + throw new IOException("Resource not found: " + resourcePath); } - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to create root folder", e); + return new String(is.readAllBytes(), StandardCharsets.UTF_8); } } + private static String encodePathSegment(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8); + } + /** * * @return A {@link SearchUtil} instance. From cd3a07e7ca2d7852c7990eca5a52baf8e8ac4250 Mon Sep 17 00:00:00 2001 From: shroffk Date: Thu, 6 Aug 2026 15:32:59 -0400 Subject: [PATCH 15/17] first pass of replacing elastic high level client with low level --- .../persistence/config/ElasticConfig.java | 59 +++++++++++- .../CompositeSnapshotDataRepository.java | 59 ++++++++---- .../ConfigurationDataRepository.java | 59 ++++++++---- .../ElasticsearchTreeRepository.java | 92 ++++++++++++++----- .../impl/elasticsearch/FilterRepository.java | 92 ++++++++++++++----- .../elasticsearch/SnapshotDataRepository.java | 59 ++++++++---- .../src/main/resources/tree_node_mapping.json | 2 +- .../impl/elasticsearch/ElasticTestConfig.java | 13 +++ .../web/config/ControllersTestConfig.java | 13 +++ 9 files changed, 349 insertions(+), 99 deletions(-) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java index d83fd48703..88dc3b18f2 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java @@ -16,6 +16,7 @@ import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.message.BasicHeader; import org.epics.vtype.VType; import org.phoebus.applications.saveandrestore.model.Node; @@ -34,7 +35,10 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.net.URLEncoder; +import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -174,23 +178,28 @@ private void elasticIndexInitialization() { try { if (!documentExists(ES_TREE_INDEX, ROOT_FOLDER_UNIQUE_ID)) { - ESTreeNode elasticsearchTreeNode = new ESTreeNode(); - elasticsearchTreeNode.setNode(ROOT_NODE); + String payload = buildRootNodePayload(); Request request = new Request("PUT", "/" + encodePathSegment(ES_TREE_INDEX) + "/_doc/" + encodePathSegment(ROOT_FOLDER_UNIQUE_ID)); request.addParameter("refresh", "true"); - request.setJsonEntity(objectMapper.writeValueAsString(elasticsearchTreeNode)); + request.setJsonEntity(payload); int statusCode = restClient.performRequest(request).getStatusCode(); if (statusCode >= 200 && statusCode < 300) { logger.info("Created root node in index '" + ES_TREE_INDEX + "'."); } else { - logger.warning("Failed to create root node in index '" + ES_TREE_INDEX + "' (HTTP " + statusCode + ")."); + logger.warning("Failed to create root node in index '" + ES_TREE_INDEX + + "' (HTTP " + statusCode + "). endpoint=" + request.getEndpoint() + + ", payload=" + payload); } } else { logger.info("Root node already exists in index '" + ES_TREE_INDEX + "'."); } + } catch (ResponseException e) { + int statusCode = e.getResponse().getStatusCode(); + logger.warning("Failed to create root node in index '" + ES_TREE_INDEX + + "' (HTTP " + statusCode + "): " + readExceptionBody(e)); } catch (IOException e) { logger.log(Level.WARNING, "Failed to initialize root node in index '" + ES_TREE_INDEX + "'.", e); } @@ -264,6 +273,32 @@ private static String encodePathSegment(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8); } + private String buildRootNodePayload() throws IOException { + Map node = new LinkedHashMap<>(); + node.put("created", ROOT_NODE.getCreated().getTime()); + node.put("lastModified", ROOT_NODE.getLastModified().getTime()); + node.put("name", ROOT_NODE.getName()); + node.put("nodeType", ROOT_NODE.getNodeType().name()); + node.put("uniqueId", ROOT_NODE.getUniqueId()); + node.put("userName", ROOT_NODE.getUserName()); + + Map rootDocument = new LinkedHashMap<>(); + rootDocument.put("childNodes", new ArrayList<>()); + rootDocument.put("node", node); + return objectMapper.writeValueAsString(rootDocument); + } + + private static String readExceptionBody(ResponseException e) { + try { + if (e.getResponse().getEntity() == null) { + return ""; + } + return EntityUtils.toString(e.getResponse().getEntity(), StandardCharsets.UTF_8); + } catch (Exception ex) { + return ""; + } + } + /** * * @return A {@link SearchUtil} instance. @@ -273,4 +308,20 @@ private static String encodePathSegment(String value) { public SearchUtil searchUtil(){ return new SearchUtil(); } + + @Bean("restClient") + public Rest5Client getRestClient() { + if (restClient == null) { + getClient(); + } + return restClient; + } + + @Bean("elasticObjectMapper") + public ObjectMapper elasticObjectMapper() { + if (objectMapper == null) { + getClient(); + } + return objectMapper; + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java index 2c2459c607..47c7178630 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java @@ -38,6 +38,9 @@ import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.HitsMetadata; import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.search.SearchResult; @@ -51,8 +54,12 @@ import org.springframework.stereotype.Repository; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ResponseStatusException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -77,6 +84,14 @@ public class CompositeSnapshotDataRepository implements CrudRepository S save(S entity) { IndexResponse response = client.index(indexRequest); if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_COMPOSITE_SNAPSHOT_INDEX).id(response.id())); - GetResponse resp = - client.get(getRequest, CompositeSnapshotData.class); - return (S) resp.source(); + return (S) getCompositeSnapshotDataById(response.id()).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save composite snapshot for unique id " + entity.getUniqueId(), e); @@ -116,16 +126,7 @@ public Iterable saveAll(Iterable entitie @Override public Optional findById(String id) { try { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_COMPOSITE_SNAPSHOT_INDEX).id(id)); - GetResponse resp = - client.get(getRequest, CompositeSnapshotData.class); - - if (!resp.found()) { - return Optional.empty(); - } - return Optional.of(resp.source()); + return getCompositeSnapshotDataById(id); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to retrieve composite snapshot with id: " + id, e); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Failed to retrieve composite snapshot with id: " + id); @@ -268,4 +269,30 @@ public SearchResult referenced(MultiValueMap searchParameters) { throw new RuntimeException(e); } } + + private Optional getCompositeSnapshotDataById(String id) throws IOException { + String endpoint = "/" + encodePathSegment(ES_COMPOSITE_SNAPSHOT_INDEX) + "/_doc/" + encodePathSegment(id); + Request request = new Request("GET", endpoint); + try { + var response = restClient.performRequest(request); + JsonNode body = objectMapper.readTree(response.getEntity().getContent()); + if (!body.path("found").asBoolean(false)) { + return Optional.empty(); + } + JsonNode source = body.get("_source"); + if (source == null || source.isNull()) { + return Optional.empty(); + } + return Optional.of(objectMapper.treeToValue(source, CompositeSnapshotData.class)); + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return Optional.empty(); + } + throw e; + } + } + + private static String encodePathSegment(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java index 651df50c1c..3c7fb92430 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java @@ -25,6 +25,9 @@ import co.elastic.clients.elasticsearch.core.*; import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.phoebus.applications.saveandrestore.model.ConfigurationData; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.service.saveandrestore.search.SearchUtil; @@ -36,8 +39,12 @@ import org.springframework.stereotype.Repository; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ResponseStatusException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; @@ -59,6 +66,14 @@ public class ConfigurationDataRepository implements CrudRepository S save(S entity) { IndexResponse response = client.index(indexRequest); if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_CONFIGURATION_INDEX).id(response.id())); - GetResponse resp = - client.get(getRequest, ConfigurationData.class); - return (S) resp.source(); + return (S) getConfigurationDataById(response.id()).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save configuration for config id " + entity.getUniqueId(), e); @@ -98,16 +108,7 @@ public Iterable saveAll(Iterable entities) { @Override public Optional findById(String id) { try { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_CONFIGURATION_INDEX).id(id)); - GetResponse resp = - client.get(getRequest, ConfigurationData.class); - - if (!resp.found()) { - return Optional.empty(); - } - return resp.source() != null ? Optional.of(resp.source()) : Optional.empty(); + return getConfigurationDataById(id); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to retrieve configuration with id: " + id, e); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Failed to retrieve configuration with id: " + id); @@ -214,4 +215,30 @@ public List searchOnPvName(MultiValueMap sear throw new RuntimeException(e); } } + + private Optional getConfigurationDataById(String id) throws IOException { + String endpoint = "/" + encodePathSegment(ES_CONFIGURATION_INDEX) + "/_doc/" + encodePathSegment(id); + Request request = new Request("GET", endpoint); + try { + var response = restClient.performRequest(request); + JsonNode body = objectMapper.readTree(response.getEntity().getContent()); + if (!body.path("found").asBoolean(false)) { + return Optional.empty(); + } + JsonNode source = body.get("_source"); + if (source == null || source.isNull()) { + return Optional.empty(); + } + return Optional.of(objectMapper.treeToValue(source, ConfigurationData.class)); + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return Optional.empty(); + } + throw e; + } + } + + private static String encodePathSegment(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java index 16cce96aec..39e8743c6f 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java @@ -26,6 +26,9 @@ import co.elastic.clients.elasticsearch.core.*; import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.phoebus.applications.saveandrestore.model.Tag; import org.phoebus.applications.saveandrestore.model.search.SearchResult; import org.phoebus.service.saveandrestore.NodeNotFoundException; @@ -38,8 +41,12 @@ import org.springframework.lang.NonNull; import org.springframework.stereotype.Repository; import org.springframework.util.MultiValueMap; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -67,6 +74,14 @@ public class ElasticsearchTreeRepository implements CrudRepository S save(@NonNull S elasticTreeNode) { IndexResponse response = client.index(indexRequest); if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_TREE_INDEX).id(response.id())); - GetResponse resp = - client.get(getRequest, ESTreeNode.class); - return (S) resp.source(); + return (S) getTreeNodeById(response.id()).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save ESTreeNode object: " + elasticTreeNode, e); @@ -152,16 +162,11 @@ public Iterable saveAll(@NonNull Iterable entities) @Override public Optional findById(@NonNull String id) { try { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_TREE_INDEX).id(id)); - GetResponse resp = - client.get(getRequest, ESTreeNode.class); - - if (!resp.found()) { + Optional result = getTreeNodeById(id); + if (result.isEmpty()) { throw new NodeNotFoundException("ESTreeNode with id " + id + " not found."); } - return Optional.of(resp.source()); + return result; } catch (IOException e) { logger.log(Level.SEVERE, "Failed to retrieve ESTreeNode with id: " + id, e); throw new RuntimeException("Failed to ESTreeNode with id: " + id); @@ -212,16 +217,8 @@ public Iterable findAllById(Iterable uniqueIds) { } List ids = new ArrayList<>(); uniqueIds.forEach(ids::add); - MgetRequest mgetRequest = MgetRequest.of(m -> m.index(ES_TREE_INDEX).ids(ids)); try { - List treeNodes = new ArrayList<>(); - MgetResponse resp = client.mget(mgetRequest, ESTreeNode.class); - resp.docs().forEach(doc -> { - if (doc.result().found()) { // Only add elements that actually exist - treeNodes.add(doc.result().source()); - } - }); - return treeNodes; + return mgetTreeNodes(ids); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to retrieve multiple nodes"); throw new NodeNotFoundException("Failed to retrieve multiple nodes"); @@ -368,4 +365,53 @@ public SearchResult search(MultiValueMap searchParameters) { throw new RuntimeException(e); } } + + private Optional getTreeNodeById(String id) throws IOException { + String endpoint = "/" + encodePathSegment(ES_TREE_INDEX) + "/_doc/" + encodePathSegment(id); + Request request = new Request("GET", endpoint); + try { + var response = restClient.performRequest(request); + JsonNode body = objectMapper.readTree(response.getEntity().getContent()); + if (!body.path("found").asBoolean(false)) { + return Optional.empty(); + } + JsonNode source = body.get("_source"); + if (source == null || source.isNull()) { + return Optional.empty(); + } + return Optional.of(objectMapper.treeToValue(source, ESTreeNode.class)); + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return Optional.empty(); + } + throw e; + } + } + + private List mgetTreeNodes(List ids) throws IOException { + String endpoint = "/" + encodePathSegment(ES_TREE_INDEX) + "/_mget"; + Request request = new Request("POST", endpoint); + request.setJsonEntity(objectMapper.writeValueAsString(Map.of("ids", ids))); + + var response = restClient.performRequest(request); + JsonNode docs = objectMapper.readTree(response.getEntity().getContent()).path("docs"); + if (!docs.isArray()) { + return Collections.emptyList(); + } + + List treeNodes = new ArrayList<>(); + for (JsonNode doc : docs) { + if (doc.path("found").asBoolean(false)) { + JsonNode source = doc.get("_source"); + if (source != null && !source.isNull()) { + treeNodes.add(objectMapper.treeToValue(source, ESTreeNode.class)); + } + } + } + return treeNodes; + } + + private static String encodePathSegment(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java index 64ef434f22..d567226e02 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java @@ -37,6 +37,9 @@ import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.service.saveandrestore.NodeNotFoundException; @@ -48,8 +51,12 @@ import org.springframework.lang.NonNull; import org.springframework.stereotype.Repository; import org.springframework.web.server.ResponseStatusException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -75,6 +82,14 @@ public class FilterRepository implements CrudRepository { @Qualifier("client") private ElasticsearchClient client; + @Autowired + @Qualifier("restClient") + private Rest5Client restClient; + + @Autowired + @Qualifier("elasticObjectMapper") + private ObjectMapper objectMapper; + /** * Saves an {@link Filter} object. * @@ -94,12 +109,7 @@ public S save(S filter) { IndexResponse response = client.index(indexRequest); if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_FILTER_INDEX).id(response.id())); - GetResponse resp = - client.get(getRequest, Filter.class); - return (S) resp.source(); + return (S) getFilterById(response.id()).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save Filter object: " + filter.getName(), e); @@ -125,16 +135,11 @@ public Iterable saveAll(@NonNull Iterable entities) { @Override public Optional findById(@NonNull String name) { try { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_FILTER_INDEX).id(name)); - GetResponse resp = - client.get(getRequest, Filter.class); - - if (!resp.found()) { + Optional result = getFilterById(name); + if (result.isEmpty()) { throw new NodeNotFoundException("Filter with name " + name + " not found."); } - return Optional.of(resp.source()); + return result; } catch (IOException e) { logger.log(Level.SEVERE, "Failed to retrieve Filter with name: " + name, e); throw new RuntimeException("Failed to Filter with name: " + name); @@ -203,16 +208,8 @@ public Iterable findAllById(Iterable uniqueNames) { } List ids = new ArrayList<>(); uniqueNames.forEach(ids::add); - MgetRequest mgetRequest = MgetRequest.of(m -> m.index(ES_FILTER_INDEX).ids(ids)); try { - List filters = new ArrayList<>(); - MgetResponse resp = client.mget(mgetRequest, Filter.class); - resp.docs().forEach(doc -> { - if (doc.result().found()) { // Only add elements that actually exist - filters.add(doc.result().source()); - } - }); - return filters; + return mgetFilters(ids); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to retrieve multiple filters"); throw new RuntimeException("Failed to retrieve multiple filters"); @@ -272,4 +269,53 @@ public void deleteAll() { throw new RuntimeException(e); } } + + private Optional getFilterById(String id) throws IOException { + String endpoint = "/" + encodePathSegment(ES_FILTER_INDEX) + "/_doc/" + encodePathSegment(id); + Request request = new Request("GET", endpoint); + try { + var response = restClient.performRequest(request); + JsonNode body = objectMapper.readTree(response.getEntity().getContent()); + if (!body.path("found").asBoolean(false)) { + return Optional.empty(); + } + JsonNode source = body.get("_source"); + if (source == null || source.isNull()) { + return Optional.empty(); + } + return Optional.of(objectMapper.treeToValue(source, Filter.class)); + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return Optional.empty(); + } + throw e; + } + } + + private List mgetFilters(List ids) throws IOException { + String endpoint = "/" + encodePathSegment(ES_FILTER_INDEX) + "/_mget"; + Request request = new Request("POST", endpoint); + request.setJsonEntity(objectMapper.writeValueAsString(java.util.Map.of("ids", ids))); + + var response = restClient.performRequest(request); + JsonNode docs = objectMapper.readTree(response.getEntity().getContent()).path("docs"); + if (!docs.isArray()) { + return Collections.emptyList(); + } + + List filters = new ArrayList<>(); + for (JsonNode doc : docs) { + if (doc.path("found").asBoolean(false)) { + JsonNode source = doc.get("_source"); + if (source != null && !source.isNull()) { + filters.add(objectMapper.treeToValue(source, Filter.class)); + } + } + } + return filters; + } + + private static String encodePathSegment(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java index d5102fd974..ee999b27e8 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java @@ -28,6 +28,9 @@ import co.elastic.clients.elasticsearch.core.GetResponse; import co.elastic.clients.elasticsearch.core.IndexRequest; import co.elastic.clients.elasticsearch.core.IndexResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.phoebus.applications.saveandrestore.model.SnapshotData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -37,8 +40,12 @@ import org.springframework.lang.NonNull; import org.springframework.stereotype.Repository; import org.springframework.web.server.ResponseStatusException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -56,6 +63,14 @@ public class SnapshotDataRepository implements CrudRepository S save(@NonNull S entity) { IndexResponse response = client.index(indexRequest); if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_SNAPSHOT_INDEX).id(response.id())); - GetResponse resp = - client.get(getRequest, SnapshotData.class); - return (S) resp.source(); + return (S) getSnapshotDataById(response.id()).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save snapshot for config id " + entity.getUniqueId(), e); @@ -97,16 +107,7 @@ public Iterable saveAll(Iterable entities) { @Override public Optional findById(String id) { try { - GetRequest getRequest = - GetRequest.of(g -> - g.index(ES_SNAPSHOT_INDEX).id(id)); - GetResponse resp = - client.get(getRequest, SnapshotData.class); - - if (!resp.found()) { - return Optional.empty(); - } - return Optional.of(resp.source()); + return getSnapshotDataById(id); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to retrieve snapshot with id: " + id, e); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Failed to retrieve snapshot with id: " + id); @@ -185,4 +186,30 @@ public void deleteAll() { throw new RuntimeException(e); } } + + private Optional getSnapshotDataById(String id) throws IOException { + String endpoint = "/" + encodePathSegment(ES_SNAPSHOT_INDEX) + "/_doc/" + encodePathSegment(id); + Request request = new Request("GET", endpoint); + try { + var response = restClient.performRequest(request); + JsonNode body = objectMapper.readTree(response.getEntity().getContent()); + if (!body.path("found").asBoolean(false)) { + return Optional.empty(); + } + JsonNode source = body.get("_source"); + if (source == null || source.isNull()) { + return Optional.empty(); + } + return Optional.of(objectMapper.treeToValue(source, SnapshotData.class)); + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return Optional.empty(); + } + throw e; + } + } + + private static String encodePathSegment(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); + } } diff --git a/services/save-and-restore/src/main/resources/tree_node_mapping.json b/services/save-and-restore/src/main/resources/tree_node_mapping.json index 84821e36a0..6bde6fccb6 100644 --- a/services/save-and-restore/src/main/resources/tree_node_mapping.json +++ b/services/save-and-restore/src/main/resources/tree_node_mapping.json @@ -57,4 +57,4 @@ } } } -} \ No newline at end of file +} diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java index 62e7c7c0f7..163c2b66e5 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticTestConfig.java @@ -1,11 +1,14 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.mockito.Mockito; import org.phoebus.service.saveandrestore.search.SearchUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; @Configuration @Profile("!IT") @@ -23,6 +26,16 @@ public ElasticsearchClient getClient() { return Mockito.mock(ElasticsearchClient.class); } + @Bean("restClient") + public Rest5Client restClient() { + return Mockito.mock(Rest5Client.class); + } + + @Bean("elasticObjectMapper") + public ObjectMapper elasticObjectMapper() { + return JsonMapper.builder().build(); + } + @Bean public ElasticsearchTreeRepository elasticsearchTreeRepository(){ return Mockito.mock(ElasticsearchTreeRepository.class); diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java index fe097f5eb9..cf6e1c2114 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java @@ -19,6 +19,7 @@ package org.phoebus.service.saveandrestore.web.config; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.mockito.Mockito; import org.phoebus.saveandrestore.util.SnapshotUtil; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; @@ -38,6 +39,8 @@ import org.springframework.mock.web.MockServletContext; import java.util.Base64; import org.springframework.web.socket.WebSocketSession; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import jakarta.servlet.ServletContext; import java.util.concurrent.ExecutorService; @@ -100,6 +103,16 @@ public ElasticsearchClient client() { return Mockito.mock(ElasticsearchClient.class); } + @Bean("restClient") + public Rest5Client restClient() { + return Mockito.mock(Rest5Client.class); + } + + @Bean("elasticObjectMapper") + public ObjectMapper elasticObjectMapper() { + return JsonMapper.builder().build(); + } + @SuppressWarnings("unused") @Bean public AcceptHeaderResolver acceptHeaderResolver() { From 86d8ee95451785024e3882ddb6575f9c30516a29 Mon Sep 17 00:00:00 2001 From: shroffk Date: Fri, 7 Aug 2026 09:09:46 -0400 Subject: [PATCH 16/17] move sar to the low level elastic rest client --- .../CompositeSnapshotDataRepository.java | 163 +++++++----- .../ConfigurationDataRepository.java | 116 ++++++--- .../ElasticsearchTreeRepository.java | 244 +++++++++++++----- .../impl/elasticsearch/FilterRepository.java | 116 +++++---- .../elasticsearch/SnapshotDataRepository.java | 41 +-- .../web/controllers/InfoController.java | 22 +- 6 files changed, 443 insertions(+), 259 deletions(-) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java index 47c7178630..4de225a7aa 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java @@ -19,28 +19,12 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.Refresh; -import co.elastic.clients.elasticsearch._types.Result; -import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery; -import co.elastic.clients.elasticsearch.core.CountRequest; -import co.elastic.clients.elasticsearch.core.CountResponse; -import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest; -import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse; -import co.elastic.clients.elasticsearch.core.DeleteRequest; -import co.elastic.clients.elasticsearch.core.DeleteResponse; -import co.elastic.clients.elasticsearch.core.ExistsRequest; -import co.elastic.clients.elasticsearch.core.GetRequest; -import co.elastic.clients.elasticsearch.core.GetResponse; -import co.elastic.clients.elasticsearch.core.IndexRequest; -import co.elastic.clients.elasticsearch.core.IndexResponse; import co.elastic.clients.elasticsearch.core.SearchRequest; -import co.elastic.clients.elasticsearch.core.SearchResponse; -import co.elastic.clients.elasticsearch.core.search.Hit; -import co.elastic.clients.elasticsearch.core.search.HitsMetadata; -import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; +import jakarta.json.stream.JsonGenerator; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.search.SearchResult; @@ -56,16 +40,18 @@ import org.springframework.web.server.ResponseStatusException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.io.IOException; +import java.io.StringWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * {@link Repository} class for {@link CompositeSnapshotData}. @@ -80,10 +66,6 @@ public class CompositeSnapshotDataRepository implements CrudRepository S save(S entity) { try { - IndexRequest indexRequest = - IndexRequest.of(i -> - i.index(ES_COMPOSITE_SNAPSHOT_INDEX) - .id(entity.getUniqueId()) - .document(entity) - .refresh(Refresh.True)); - IndexResponse response = client.index(indexRequest); - - if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - return (S) getCompositeSnapshotDataById(response.id()).orElse(null); + String id = entity.getUniqueId(); + Request request = new Request("PUT", "/" + encodePathSegment(ES_COMPOSITE_SNAPSHOT_INDEX) + + "/_doc/" + encodePathSegment(id)); + request.addParameter("refresh", "true"); + request.setJsonEntity(objectMapper.writeValueAsString(entity)); + int statusCode = restClient.performRequest(request).getStatusCode(); + + if (statusCode >= 200 && statusCode < 300) { + return (S) getCompositeSnapshotDataById(id).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save composite snapshot for unique id " + entity.getUniqueId(), e); @@ -136,9 +117,7 @@ public Optional findById(String id) { @Override public boolean existsById(String s) { try { - ExistsRequest existsRequest = ExistsRequest.of(e -> e.index(ES_COMPOSITE_SNAPSHOT_INDEX).id(s)); - BooleanResponse existsResponse = client.exists(existsRequest); - return existsResponse.value(); + return documentExists(ES_COMPOSITE_SNAPSHOT_INDEX, s); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to query if CompositeSnapshot with id " + s + " exists"); } @@ -159,10 +138,10 @@ public Iterable findAll() { int from = 0; while (true) { try { - SearchResponse searchResponse = runPagedMatchAll(pageSize, from); - result.addAll(searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList())); - from += searchResponse.hits().hits().size(); - if (searchResponse.hits().hits().size() < pageSize) { + List batch = runPagedMatchAll(pageSize, from); + result.addAll(batch); + from += batch.size(); + if (batch.size() < pageSize) { break; } } catch (IOException e) { @@ -173,14 +152,11 @@ public Iterable findAll() { return result; } - private SearchResponse runPagedMatchAll(int pageSize, int from) throws IOException { - SearchRequest searchRequest = - SearchRequest.of(s -> - s.index(ES_COMPOSITE_SNAPSHOT_INDEX) - .query(new MatchAllQuery.Builder().build()._toQuery()) - .size(pageSize) - .from(from)); - return client.search(searchRequest, CompositeSnapshotData.class); + private List runPagedMatchAll(int pageSize, int from) throws IOException { + Request request = new Request("POST", "/" + encodePathSegment(ES_COMPOSITE_SNAPSHOT_INDEX) + "/_search"); + request.setJsonEntity("{\"query\":{\"match_all\":{}},\"size\":" + pageSize + ",\"from\":" + from + "}"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + return parseSearchHits(body, CompositeSnapshotData.class); } @Override @@ -191,10 +167,9 @@ public Iterable findAllById(Iterable strings) { @Override public long count() { try { - CountRequest countRequest = CountRequest.of(c -> - c.index(ES_COMPOSITE_SNAPSHOT_INDEX)); - CountResponse countResponse = client.count(countRequest); - return countResponse.count(); + Request request = new Request("POST", "/" + encodePathSegment(ES_COMPOSITE_SNAPSHOT_INDEX) + "/_count"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + return body.path("count").asLong(0L); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to count CompositeSnapshot objects", e); throw new RuntimeException(e); @@ -204,10 +179,11 @@ public long count() { @Override public void deleteById(String s) { try { - DeleteRequest deleteRequest = DeleteRequest.of(d -> - d.index(ES_COMPOSITE_SNAPSHOT_INDEX).id(s).refresh(Refresh.True)); - DeleteResponse deleteResponse = client.delete(deleteRequest); - if (deleteResponse.result().equals(Result.Deleted)) { + Request request = new Request("DELETE", "/" + encodePathSegment(ES_COMPOSITE_SNAPSHOT_INDEX) + + "/_doc/" + encodePathSegment(s)); + request.addParameter("refresh", "true"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + if ("deleted".equalsIgnoreCase(body.path("result").asText(""))) { logger.log(Level.WARNING, "Composite snapshot with id " + s + " deleted."); } else { logger.log(Level.WARNING, "Composite snapshot with id " + s + " NOT deleted."); @@ -236,10 +212,11 @@ public void deleteAll(Iterable entities) { @Override public void deleteAll() { try { - DeleteByQueryRequest deleteRequest = DeleteByQueryRequest.of(d -> - d.index(ES_COMPOSITE_SNAPSHOT_INDEX).query(new MatchAllQuery.Builder().build()._toQuery()).refresh(true)); - DeleteByQueryResponse deleteResponse = client.deleteByQuery(deleteRequest); - logger.log(Level.INFO, "Deleted " + deleteResponse.deleted() + " CompositeSnapshot objects"); + Request request = new Request("POST", "/" + encodePathSegment(ES_COMPOSITE_SNAPSHOT_INDEX) + "/_delete_by_query"); + request.addParameter("refresh", "true"); + request.setJsonEntity("{\"query\":{\"match_all\":{}}}"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + logger.log(Level.INFO, "Deleted " + body.path("deleted").asLong(0L) + " CompositeSnapshot objects"); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to delete all CompositeSnapshot objects", e); throw new RuntimeException(e); @@ -257,13 +234,14 @@ public SearchResult referenced(MultiValueMap searchParameters) { SearchRequest searchRequest = searchUtil.buildSearchRequest(searchParameters); try { - SearchResponse response = client.search(searchRequest, CompositeSnapshotData.class); - HitsMetadata hitsMetadata = response.hits(); - List compositeSnapshotDataList = hitsMetadata.hits().stream().map(Hit::source).toList(); + Request request = new Request("POST", "/" + encodePathSegment(ES_COMPOSITE_SNAPSHOT_INDEX) + "/_search"); + request.setJsonEntity(serializeSearchRequest(searchRequest)); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + List compositeSnapshotDataList = parseSearchHits(body, CompositeSnapshotData.class); Iterable esTreeNodes = elasticsearchTreeRepository.findAllById(compositeSnapshotDataList.stream().map(CompositeSnapshotData::getUniqueId).toList()); List list = new ArrayList<>(); esTreeNodes.iterator().forEachRemaining(es -> list.add(es.getNode())); - return new SearchResult((int) hitsMetadata.total().value(), list); + return new SearchResult((int) getTotalHits(body), list); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to search for referenced snapshot nodes", e); throw new RuntimeException(e); @@ -295,4 +273,61 @@ private Optional getCompositeSnapshotDataById(String id) private static String encodePathSegment(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); } + + private boolean documentExists(String indexName, String documentId) throws IOException { + try { + Request request = new Request("HEAD", "/" + encodePathSegment(indexName) + + "/_doc/" + encodePathSegment(documentId)); + int statusCode = restClient.performRequest(request).getStatusCode(); + return statusCode >= 200 && statusCode < 300; + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return false; + } + throw e; + } + } + + private List parseSearchHits(JsonNode response, Class type) throws IOException { + JsonNode hits = response.path("hits").path("hits"); + if (!hits.isArray()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (JsonNode hit : hits) { + JsonNode source = hit.get("_source"); + if (source != null && !source.isNull()) { + result.add(objectMapper.treeToValue(source, type)); + } + } + return result; + } + + private long getTotalHits(JsonNode response) { + JsonNode total = response.path("hits").path("total"); + if (total.isObject()) { + return total.path("value").asLong(0L); + } + if (total.isNumber()) { + return total.asLong(0L); + } + return 0L; + } + + private String serializeSearchRequest(SearchRequest searchRequest) { + try { + StringWriter writer = new StringWriter(); + JsonMapper jsonMapper = objectMapper instanceof JsonMapper + ? (JsonMapper) objectMapper + : JsonMapper.builder().build(); + Jackson3JsonpMapper mapper = new Jackson3JsonpMapper(jsonMapper); + JsonGenerator generator = mapper.jsonProvider().createGenerator(writer); + searchRequest.serialize(generator, mapper); + generator.close(); + return writer.toString(); + } catch (Exception e) { + throw new RuntimeException("Failed to serialize search request", e); + } + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java index 3c7fb92430..89c332d9f8 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java @@ -19,15 +19,12 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.Refresh; -import co.elastic.clients.elasticsearch._types.Result; -import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery; -import co.elastic.clients.elasticsearch.core.*; -import co.elastic.clients.elasticsearch.core.search.Hit; -import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; +import jakarta.json.stream.JsonGenerator; import org.phoebus.applications.saveandrestore.model.ConfigurationData; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.service.saveandrestore.search.SearchUtil; @@ -41,8 +38,10 @@ import org.springframework.web.server.ResponseStatusException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.io.IOException; +import java.io.StringWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -51,7 +50,6 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * Repository for {@link ConfigurationData}. @@ -62,10 +60,6 @@ public class ConfigurationDataRepository implements CrudRepository S save(S entity) { try { - IndexRequest indexRequest = - IndexRequest.of(i -> - i.index(ES_CONFIGURATION_INDEX) - .id(entity.getUniqueId()) - .document(entity) - .refresh(Refresh.True)); - IndexResponse response = client.index(indexRequest); - - if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - return (S) getConfigurationDataById(response.id()).orElse(null); + String id = entity.getUniqueId(); + Request request = new Request("PUT", "/" + encodePathSegment(ES_CONFIGURATION_INDEX) + + "/_doc/" + encodePathSegment(id)); + request.addParameter("refresh", "true"); + request.setJsonEntity(objectMapper.writeValueAsString(entity)); + int statusCode = restClient.performRequest(request).getStatusCode(); + + if (statusCode >= 200 && statusCode < 300) { + return (S) getConfigurationDataById(id).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save configuration for config id " + entity.getUniqueId(), e); @@ -118,9 +111,7 @@ public Optional findById(String id) { @Override public boolean existsById(String s) { try { - ExistsRequest existsRequest = ExistsRequest.of(e -> e.index(ES_CONFIGURATION_INDEX).id(s)); - BooleanResponse existsResponse = client.exists(existsRequest); - return existsResponse.value(); + return documentExists(ES_CONFIGURATION_INDEX, s); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to query if ConfigurationData with id " + s + " exists"); } @@ -140,10 +131,9 @@ public Iterable findAllById(Iterable strings) { @Override public long count() { try { - CountRequest countRequest = CountRequest.of(c -> - c.index(ES_CONFIGURATION_INDEX)); - CountResponse countResponse = client.count(countRequest); - return countResponse.count(); + Request request = new Request("POST", "/" + encodePathSegment(ES_CONFIGURATION_INDEX) + "/_count"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + return body.path("count").asLong(0L); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to count ConfigurationData objects", e); throw new RuntimeException(e); @@ -153,10 +143,11 @@ public long count() { @Override public void deleteById(String s) { try { - DeleteRequest deleteRequest = DeleteRequest.of(d -> - d.index(ES_CONFIGURATION_INDEX).id(s).refresh(Refresh.True)); - DeleteResponse deleteResponse = client.delete(deleteRequest); - if (deleteResponse.result().equals(Result.Deleted)) { + Request request = new Request("DELETE", "/" + encodePathSegment(ES_CONFIGURATION_INDEX) + + "/_doc/" + encodePathSegment(s)); + request.addParameter("refresh", "true"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + if ("deleted".equalsIgnoreCase(body.path("result").asText(""))) { logger.log(Level.WARNING, "Configuration with id " + s + " deleted."); } else { logger.log(Level.WARNING, "Configuration with id " + s + " NOT deleted."); @@ -185,10 +176,11 @@ public void deleteAll(Iterable entities) { @Override public void deleteAll() { try { - DeleteByQueryRequest deleteRequest = DeleteByQueryRequest.of(d -> - d.index(ES_CONFIGURATION_INDEX).query(new MatchAllQuery.Builder().build()._toQuery()).refresh(true)); - DeleteByQueryResponse deleteResponse = client.deleteByQuery(deleteRequest); - logger.log(Level.INFO, "Deleted " + deleteResponse.deleted() + " ConfigurationData objects"); + Request request = new Request("POST", "/" + encodePathSegment(ES_CONFIGURATION_INDEX) + "/_delete_by_query"); + request.addParameter("refresh", "true"); + request.setJsonEntity("{\"query\":{\"match_all\":{}}}"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + logger.log(Level.INFO, "Deleted " + body.path("deleted").asLong(0L) + " ConfigurationData objects"); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to delete all ConfigurationData objects", e); throw new RuntimeException(e); @@ -209,8 +201,10 @@ public List searchOnPvName(MultiValueMap sear } SearchRequest searchRequest = searchUtil.buildSearchRequestForPvs(optional.get().getValue()); try { - SearchResponse searchResponse = client.search(searchRequest, ConfigurationData.class); - return searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList()); + Request request = new Request("POST", "/" + encodePathSegment(ES_CONFIGURATION_INDEX) + "/_search"); + request.setJsonEntity(serializeSearchRequest(searchRequest)); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + return parseSearchHits(body, ConfigurationData.class); } catch (IOException e) { throw new RuntimeException(e); } @@ -241,4 +235,50 @@ private Optional getConfigurationDataById(String id) throws I private static String encodePathSegment(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); } + + private boolean documentExists(String indexName, String documentId) throws IOException { + try { + Request request = new Request("HEAD", "/" + encodePathSegment(indexName) + + "/_doc/" + encodePathSegment(documentId)); + int statusCode = restClient.performRequest(request).getStatusCode(); + return statusCode >= 200 && statusCode < 300; + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return false; + } + throw e; + } + } + + private List parseSearchHits(JsonNode response, Class type) throws IOException { + JsonNode hits = response.path("hits").path("hits"); + if (!hits.isArray()) { + return Collections.emptyList(); + } + + List result = new java.util.ArrayList<>(); + for (JsonNode hit : hits) { + JsonNode source = hit.get("_source"); + if (source != null && !source.isNull()) { + result.add(objectMapper.treeToValue(source, type)); + } + } + return result; + } + + private String serializeSearchRequest(SearchRequest searchRequest) { + try { + StringWriter writer = new StringWriter(); + JsonMapper jsonMapper = objectMapper instanceof JsonMapper + ? (JsonMapper) objectMapper + : JsonMapper.builder().build(); + Jackson3JsonpMapper mapper = new Jackson3JsonpMapper(jsonMapper); + JsonGenerator generator = mapper.jsonProvider().createGenerator(writer); + searchRequest.serialize(generator, mapper); + generator.close(); + return writer.toString(); + } catch (Exception e) { + throw new RuntimeException("Failed to serialize search request", e); + } + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java index 39e8743c6f..486ecfd5b1 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java @@ -19,16 +19,12 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.Refresh; -import co.elastic.clients.elasticsearch._types.Result; -import co.elastic.clients.elasticsearch._types.query_dsl.*; -import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery.Builder; -import co.elastic.clients.elasticsearch.core.*; -import co.elastic.clients.elasticsearch.core.search.Hit; -import co.elastic.clients.transport.endpoints.BooleanResponse; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; +import jakarta.json.stream.JsonGenerator; import org.phoebus.applications.saveandrestore.model.Tag; import org.phoebus.applications.saveandrestore.model.search.SearchResult; import org.phoebus.service.saveandrestore.NodeNotFoundException; @@ -43,14 +39,15 @@ import org.springframework.util.MultiValueMap; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.io.IOException; +import java.io.StringWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * Repository for {@link ESTreeNode} objects. @@ -70,10 +67,6 @@ public class ElasticsearchTreeRepository implements CrudRepository S save(@NonNull S elasticTreeNode) { elasticTreeNode.getNode().setLastModified(now); } - IndexRequest indexRequest = - IndexRequest.of(i -> - i.index(ES_TREE_INDEX) - .id(elasticTreeNode.getNode().getUniqueId()) - .document(elasticTreeNode) - .refresh(Refresh.True)); - IndexResponse response = client.index(indexRequest); + String id = elasticTreeNode.getNode().getUniqueId(); + Request request = new Request("PUT", "/" + encodePathSegment(ES_TREE_INDEX) + + "/_doc/" + encodePathSegment(id)); + request.addParameter("refresh", "true"); + request.setJsonEntity(buildTreeNodePayload(elasticTreeNode)); + int statusCode = restClient.performRequest(request).getStatusCode(); - if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - return (S) getTreeNodeById(response.id()).orElse(null); + if (statusCode >= 200 && statusCode < 300) { + return (S) getTreeNodeById(id).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save ESTreeNode object: " + elasticTreeNode, e); @@ -181,9 +173,7 @@ public Optional findById(@NonNull String id) { public boolean existsById(@NonNull String id) { try { - ExistsRequest existsRequest = ExistsRequest.of(e -> e.index(ES_TREE_INDEX).id(id)); - BooleanResponse existsResponse = client.exists(existsRequest); - return existsResponse.value(); + return documentExists(ES_TREE_INDEX, id); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to query if ESTreeNode with id " + id + " exists"); } @@ -241,10 +231,12 @@ public long count() { @Override public void deleteById(@NonNull String id) { try { - DeleteRequest deleteRequest = DeleteRequest.of(d -> - d.index(ES_TREE_INDEX).id(id).refresh(Refresh.True)); - DeleteResponse deleteResponse = client.delete(deleteRequest); - if (deleteResponse.result().equals(Result.Deleted)) { + Request request = new Request("DELETE", "/" + encodePathSegment(ES_TREE_INDEX) + + "/_doc/" + encodePathSegment(id)); + request.addParameter("refresh", "true"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + String result = body.path("result").asText(""); + if ("deleted".equalsIgnoreCase(result)) { logger.log(Level.WARNING, "Node with id " + id + " deleted."); } else { logger.log(Level.WARNING, "Node with id " + id + " NOT deleted."); @@ -273,10 +265,11 @@ public void deleteAll(Iterable entities) { @Override public void deleteAll() { try { - DeleteByQueryRequest deleteRequest = DeleteByQueryRequest.of(d -> - d.index(ES_TREE_INDEX).query(new MatchAllQuery.Builder().build()._toQuery()).refresh(true)); - DeleteByQueryResponse deleteResponse = client.deleteByQuery(deleteRequest); - logger.log(Level.INFO, "Deleted " + deleteResponse.deleted() + " ESTreeNode objects"); + Request request = new Request("POST", "/" + encodePathSegment(ES_TREE_INDEX) + "/_delete_by_query"); + request.addParameter("refresh", "true"); + request.setJsonEntity("{\"query\":{\"match_all\":{}}}"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + logger.log(Level.INFO, "Deleted " + body.path("deleted").asLong(0L) + " ESTreeNode objects"); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to delete all ESTreeNode objects", e); throw new RuntimeException(e); @@ -293,20 +286,20 @@ public void deleteAll() { * null. */ public ESTreeNode getParentNode(String uniqueId) { - Builder bqb = new Builder(); - bqb.must(TermQuery.of(w -> w.field("childNodes").value(uniqueId))._toQuery()); - SearchRequest searchRequest = SearchRequest.of(s -> s.index(ES_TREE_INDEX) - .query(bqb.build()._toQuery()) - .timeout("60s")); try { - SearchResponse searchResponse = client.search(searchRequest, ESTreeNode.class); - if (!searchResponse.hits().hits().isEmpty()) { - if (searchResponse.hits().hits().size() > 1) { + Map query = Map.of( + "query", Map.of("bool", Map.of("must", List.of(Map.of("term", Map.of("childNodes", uniqueId))))), + "timeout", "60s", + "size", 2 + ); + JsonNode response = executeSearch("/" + encodePathSegment(ES_TREE_INDEX) + "/_search", + objectMapper.writeValueAsString(query)); + List result = parseSearchHits(response, ESTreeNode.class); + if (!result.isEmpty()) { + if (result.size() > 1) { logger.log(Level.SEVERE, "Node " + uniqueId + " is child node of multiple nodes. Should not happen!"); throw new RuntimeException("Node " + uniqueId + " contained in multiple parent nodes. Should not happen!"); } - List result = - searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList()); return result.get(0); } else { throw new NodeNotFoundException("Unable to locate parent node for unique id " + uniqueId); @@ -323,24 +316,28 @@ public ESTreeNode getParentNode(String uniqueId) { * @return A potentially empty list of {@link Tag}s. */ public List searchNodesForTag(boolean goldenOnly) { - BoolQuery.Builder boolQueryBuilder = new Builder(); - NestedQuery innerNestedQuery; - if (!goldenOnly) { - ExistsQuery existsQuery = ExistsQuery.of(e -> e.field("node.tags")); - innerNestedQuery = NestedQuery.of(n1 -> n1.path("node.tags").query(existsQuery._toQuery())); - } else { - MatchQuery matchQuery = MatchQuery.of(m -> m.field("node.tags.name").query(Tag.GOLDEN)); - innerNestedQuery = NestedQuery.of(n1 -> n1.path("node.tags").query(matchQuery._toQuery())); - } - NestedQuery outerNestedQuery = NestedQuery.of(n2 -> n2.path("node").query(innerNestedQuery._toQuery())); - boolQueryBuilder.must(outerNestedQuery._toQuery()); - SearchRequest searchRequest = SearchRequest.of(s -> s.index(ES_TREE_INDEX) - .query(boolQueryBuilder.build()._toQuery()) - .timeout("60s") - .size(1000)); try { - SearchResponse esTreeNodeSearchResponse = client.search(searchRequest, ESTreeNode.class); - return esTreeNodeSearchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList()); + Map innerQuery; + if (!goldenOnly) { + innerQuery = Map.of("exists", Map.of("field", "node.tags")); + } else { + innerQuery = Map.of("match", Map.of("node.tags.name", Tag.GOLDEN)); + } + + Map query = Map.of( + "query", Map.of("bool", Map.of("must", List.of( + Map.of("nested", Map.of( + "path", "node", + "query", Map.of("nested", Map.of("path", "node.tags", "query", innerQuery)) + )) + ))), + "timeout", "60s", + "size", 1000 + ); + + JsonNode response = executeSearch("/" + encodePathSegment(ES_TREE_INDEX) + "/_search", + objectMapper.writeValueAsString(query)); + return parseSearchHits(response, ESTreeNode.class); } catch (IOException e) { throw new RuntimeException(e); } @@ -356,10 +353,13 @@ public SearchResult search(MultiValueMap searchParameters) { SearchRequest searchRequest = searchUtil.buildSearchRequest(searchParameters); try { - SearchResponse searchResponse = client.search(searchRequest, ESTreeNode.class); + String endpoint = "/" + encodePathSegment(ES_TREE_INDEX) + "/_search"; + String payload = serializeSearchRequest(searchRequest); + JsonNode searchResponse = executeSearch(endpoint, payload); + List hits = parseSearchHits(searchResponse, ESTreeNode.class); SearchResult searchResult = new SearchResult(); - searchResult.setHitCount((int) searchResponse.hits().total().value()); - searchResult.setNodes(searchResponse.hits().hits().stream().map(e -> e.source().getNode()).collect(Collectors.toList())); + searchResult.setHitCount((int) getTotalHits(searchResponse)); + searchResult.setNodes(hits.stream().map(e -> e.getNode()).filter(Objects::nonNull).toList()); return searchResult; } catch (IOException e) { throw new RuntimeException(e); @@ -414,4 +414,124 @@ private List mgetTreeNodes(List ids) throws IOException { private static String encodePathSegment(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); } + + private boolean documentExists(String indexName, String documentId) throws IOException { + try { + Request request = new Request("HEAD", "/" + encodePathSegment(indexName) + + "/_doc/" + encodePathSegment(documentId)); + int statusCode = restClient.performRequest(request).getStatusCode(); + return statusCode >= 200 && statusCode < 300; + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return false; + } + throw e; + } + } + + private JsonNode executeSearch(String endpoint, String payload) throws IOException { + Request request = new Request("POST", endpoint); + request.setJsonEntity(payload); + return objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + } + + private List parseSearchHits(JsonNode response, Class type) throws IOException { + JsonNode hits = response.path("hits").path("hits"); + if (!hits.isArray()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (JsonNode hit : hits) { + JsonNode source = hit.get("_source"); + if (source != null && !source.isNull()) { + result.add(objectMapper.treeToValue(source, type)); + } + } + return result; + } + + private long getTotalHits(JsonNode response) { + JsonNode total = response.path("hits").path("total"); + if (total.isObject()) { + return total.path("value").asLong(0L); + } + if (total.isNumber()) { + return total.asLong(0L); + } + return 0L; + } + + private String serializeSearchRequest(SearchRequest searchRequest) { + try { + StringWriter writer = new StringWriter(); + JsonMapper jsonMapper = objectMapper instanceof JsonMapper + ? (JsonMapper) objectMapper + : JsonMapper.builder().build(); + Jackson3JsonpMapper mapper = new Jackson3JsonpMapper(jsonMapper); + JsonGenerator generator = mapper.jsonProvider().createGenerator(writer); + searchRequest.serialize(generator, mapper); + generator.close(); + return writer.toString(); + } catch (Exception e) { + throw new RuntimeException("Failed to serialize search request", e); + } + } + + private String buildTreeNodePayload(ESTreeNode treeNode) throws IOException { + Map doc = new LinkedHashMap<>(); + doc.put("childNodes", treeNode.getChildNodes() == null ? new ArrayList<>() : treeNode.getChildNodes()); + + Map nodeMap = new LinkedHashMap<>(); + var node = treeNode.getNode(); + if (node != null) { + if (node.getUniqueId() != null) { + nodeMap.put("uniqueId", node.getUniqueId()); + } + if (node.getName() != null) { + nodeMap.put("name", node.getName()); + } + if (node.getDescription() != null) { + nodeMap.put("description", node.getDescription()); + } + if (node.getCreated() != null) { + nodeMap.put("created", node.getCreated().getTime()); + } + if (node.getLastModified() != null) { + nodeMap.put("lastModified", node.getLastModified().getTime()); + } + if (node.getNodeType() != null) { + nodeMap.put("nodeType", node.getNodeType().name()); + } + if (node.getUserName() != null) { + nodeMap.put("userName", node.getUserName()); + } + if (node.getTags() != null) { + List> tags = new ArrayList<>(); + for (Tag tag : node.getTags()) { + if (tag == null) { + continue; + } + Map tagMap = new LinkedHashMap<>(); + if (tag.getName() != null) { + tagMap.put("name", tag.getName()); + } + if (tag.getComment() != null) { + tagMap.put("comment", tag.getComment()); + } + if (tag.getUserName() != null) { + tagMap.put("userName", tag.getUserName()); + } + if (tag.getCreated() != null) { + tagMap.put("created", tag.getCreated().getTime()); + } + tags.add(tagMap); + } + nodeMap.put("tags", tags); + } + } + + doc.put("node", nodeMap); + return objectMapper.writeValueAsString(doc); + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java index d567226e02..2be15898d0 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java @@ -19,28 +19,9 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.Refresh; -import co.elastic.clients.elasticsearch._types.Result; -import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery; -import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest; -import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse; -import co.elastic.clients.elasticsearch.core.DeleteRequest; -import co.elastic.clients.elasticsearch.core.DeleteResponse; -import co.elastic.clients.elasticsearch.core.ExistsRequest; -import co.elastic.clients.elasticsearch.core.GetRequest; -import co.elastic.clients.elasticsearch.core.GetResponse; -import co.elastic.clients.elasticsearch.core.IndexRequest; -import co.elastic.clients.elasticsearch.core.IndexResponse; -import co.elastic.clients.elasticsearch.core.MgetRequest; -import co.elastic.clients.elasticsearch.core.MgetResponse; -import co.elastic.clients.elasticsearch.core.SearchRequest; -import co.elastic.clients.elasticsearch.core.SearchResponse; -import co.elastic.clients.elasticsearch.core.search.Hit; -import co.elastic.clients.transport.endpoints.BooleanResponse; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; -import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.service.saveandrestore.NodeNotFoundException; import org.springframework.beans.factory.annotation.Autowired; @@ -64,7 +45,6 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * Repository class for {@link Filter} objects. @@ -78,10 +58,6 @@ public class FilterRepository implements CrudRepository { @Value("${elasticsearch.filter.index:saveandrestore_filter}") private String ES_FILTER_INDEX; - @Autowired - @Qualifier("client") - private ElasticsearchClient client; - @Autowired @Qualifier("restClient") private Rest5Client restClient; @@ -100,16 +76,15 @@ public class FilterRepository implements CrudRepository { public S save(S filter) { try { filter.setLastUpdated(new Date()); - IndexRequest indexRequest = - IndexRequest.of(i -> - i.index(ES_FILTER_INDEX) - .id(filter.getName()) - .document(filter) - .refresh(Refresh.True)); - IndexResponse response = client.index(indexRequest); - - if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - return (S) getFilterById(response.id()).orElse(null); + String id = filter.getName(); + Request request = new Request("PUT", "/" + encodePathSegment(ES_FILTER_INDEX) + + "/_doc/" + encodePathSegment(id)); + request.addParameter("refresh", "true"); + request.setJsonEntity(objectMapper.writeValueAsString(filter)); + int statusCode = restClient.performRequest(request).getStatusCode(); + + if (statusCode >= 200 && statusCode < 300) { + return (S) getFilterById(id).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save Filter object: " + filter.getName(), e); @@ -150,9 +125,7 @@ public Optional findById(@NonNull String name) { public boolean existsById(String name) { try { - ExistsRequest existsRequest = ExistsRequest.of(e -> e.index(ES_FILTER_INDEX).id(name)); - BooleanResponse existsResponse = client.exists(existsRequest); - return existsResponse.value(); + return documentExists(ES_FILTER_INDEX, name); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to query if Filter with name " + name + " exists"); } @@ -166,10 +139,10 @@ public Iterable findAll() { int from = 0; while(true){ try { - SearchResponse searchResponse = runPagedMatchAll(pageSize, from); - result.addAll(searchResponse.hits().hits().stream().map(Hit::source).collect(Collectors.toList())); - from += searchResponse.hits().hits().size(); - if(searchResponse.hits().hits().size() < pageSize){ + List batch = runPagedMatchAll(pageSize, from); + result.addAll(batch); + from += batch.size(); + if(batch.size() < pageSize){ break; } } catch (IOException e) { @@ -180,14 +153,11 @@ public Iterable findAll() { return result; } - private SearchResponse runPagedMatchAll(int pageSize, int from) throws IOException{ - SearchRequest searchRequest = - SearchRequest.of(s -> - s.index(ES_FILTER_INDEX) - .query(new MatchAllQuery.Builder().build()._toQuery()) - .size(pageSize) - .from(from)); - return client.search(searchRequest, Filter.class); + private List runPagedMatchAll(int pageSize, int from) throws IOException{ + Request request = new Request("POST", "/" + encodePathSegment(ES_FILTER_INDEX) + "/_search"); + request.setJsonEntity("{\"query\":{\"match_all\":{}},\"size\":" + pageSize + ",\"from\":" + from + "}"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + return parseSearchHits(body, Filter.class); } /** @@ -229,10 +199,11 @@ public long count() { @Override public void deleteById(String name) { try { - DeleteRequest deleteRequest = DeleteRequest.of(d -> - d.index(ES_FILTER_INDEX).id(name).refresh(Refresh.True)); - DeleteResponse deleteResponse = client.delete(deleteRequest); - if (deleteResponse.result().equals(Result.Deleted)) { + Request request = new Request("DELETE", "/" + encodePathSegment(ES_FILTER_INDEX) + + "/_doc/" + encodePathSegment(name)); + request.addParameter("refresh", "true"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + if ("deleted".equalsIgnoreCase(body.path("result").asText(""))) { logger.log(Level.WARNING, "Filter with name " + name + " deleted."); } else { logger.log(Level.WARNING, "Filter with id " + name + " NOT deleted."); @@ -260,10 +231,11 @@ public void deleteAll(Iterable filters) { @Override public void deleteAll() { try { - DeleteByQueryRequest deleteRequest = DeleteByQueryRequest.of(d -> - d.index(ES_FILTER_INDEX).query(new MatchAllQuery.Builder().build()._toQuery()).refresh(true)); - DeleteByQueryResponse deleteResponse = client.deleteByQuery(deleteRequest); - logger.log(Level.INFO, "Deleted " + deleteResponse.deleted() + " Filter objects"); + Request request = new Request("POST", "/" + encodePathSegment(ES_FILTER_INDEX) + "/_delete_by_query"); + request.addParameter("refresh", "true"); + request.setJsonEntity("{\"query\":{\"match_all\":{}}}"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + logger.log(Level.INFO, "Deleted " + body.path("deleted").asLong(0L) + " Filter objects"); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to delete all Filter objects", e); throw new RuntimeException(e); @@ -318,4 +290,34 @@ private List mgetFilters(List ids) throws IOException { private static String encodePathSegment(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); } + + private boolean documentExists(String indexName, String documentId) throws IOException { + try { + Request request = new Request("HEAD", "/" + encodePathSegment(indexName) + + "/_doc/" + encodePathSegment(documentId)); + int statusCode = restClient.performRequest(request).getStatusCode(); + return statusCode >= 200 && statusCode < 300; + } catch (ResponseException e) { + if (e.getResponse().getStatusCode() == 404) { + return false; + } + throw e; + } + } + + private List parseSearchHits(JsonNode response, Class type) throws IOException { + JsonNode hits = response.path("hits").path("hits"); + if (!hits.isArray()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (JsonNode hit : hits) { + JsonNode source = hit.get("_source"); + if (source != null && !source.isNull()) { + result.add(objectMapper.treeToValue(source, type)); + } + } + return result; + } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java index ee999b27e8..3f3798aa36 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java @@ -19,15 +19,6 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.Refresh; -import co.elastic.clients.elasticsearch._types.Result; -import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery; -import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest; -import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse; -import co.elastic.clients.elasticsearch.core.GetRequest; -import co.elastic.clients.elasticsearch.core.GetResponse; -import co.elastic.clients.elasticsearch.core.IndexRequest; -import co.elastic.clients.elasticsearch.core.IndexResponse; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; @@ -59,10 +50,6 @@ public class SnapshotDataRepository implements CrudRepository S save(@NonNull S entity) { try { - IndexRequest indexRequest = - IndexRequest.of(i -> - i.index(ES_SNAPSHOT_INDEX) - .id(entity.getUniqueId()) - .document(entity) - .refresh(Refresh.True)); - IndexResponse response = client.index(indexRequest); - - if (response.result().equals(Result.Created) || response.result().equals(Result.Updated)) { - return (S) getSnapshotDataById(response.id()).orElse(null); + String id = entity.getUniqueId(); + Request request = new Request("PUT", "/" + encodePathSegment(ES_SNAPSHOT_INDEX) + + "/_doc/" + encodePathSegment(id)); + request.addParameter("refresh", "true"); + request.setJsonEntity(objectMapper.writeValueAsString(entity)); + int statusCode = restClient.performRequest(request).getStatusCode(); + + if (statusCode >= 200 && statusCode < 300) { + return (S) getSnapshotDataById(id).orElse(null); } } catch (Exception e) { logger.log(Level.SEVERE, "Failed to save snapshot for config id " + entity.getUniqueId(), e); @@ -177,10 +163,11 @@ public void deleteAll(Iterable entities) { @Override public void deleteAll() { try { - DeleteByQueryRequest deleteRequest = DeleteByQueryRequest.of(d -> - d.index(ES_SNAPSHOT_INDEX).query(new MatchAllQuery.Builder().build()._toQuery()).refresh(true)); - DeleteByQueryResponse deleteResponse = client.deleteByQuery(deleteRequest); - logger.log(Level.INFO, "Deleted " + deleteResponse.deleted() + " Snapshot objects"); + Request request = new Request("POST", "/" + encodePathSegment(ES_SNAPSHOT_INDEX) + "/_delete_by_query"); + request.addParameter("refresh", "true"); + request.setJsonEntity("{\"query\":{\"match_all\":{}}}"); + JsonNode body = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); + logger.log(Level.INFO, "Deleted " + body.path("deleted").asLong(0L) + " Snapshot objects"); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to delete all Snapshot objects", e); throw new RuntimeException(e); diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java index d9df6c6b80..ea0647b6e2 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java @@ -1,9 +1,9 @@ package org.phoebus.service.saveandrestore.web.controllers; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo; -import co.elastic.clients.elasticsearch.core.InfoResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import tools.jackson.core.JacksonException; +import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.json.JsonMapper; @@ -37,8 +37,8 @@ public class InfoController extends BaseController { private String version; @Autowired - @Qualifier("client") - ElasticsearchClient client; + @Qualifier("restClient") + Rest5Client restClient; private static final ObjectMapper objectMapper = JsonMapper.builder() .enable(SerializationFeature.INDENT_OUTPUT) @@ -57,12 +57,12 @@ public String info() { Map elasticInfo = new LinkedHashMap<>(); try { - InfoResponse response = client.info(); + Request request = new Request("GET", "/"); + JsonNode response = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); elasticInfo.put("status", "Connected"); - elasticInfo.put("clusterName", response.clusterName()); - elasticInfo.put("clusterUuid", response.clusterUuid()); - ElasticsearchVersionInfo version = response.version(); - elasticInfo.put("version", version.toString()); + elasticInfo.put("clusterName", response.path("cluster_name").asText("")); + elasticInfo.put("clusterUuid", response.path("cluster_uuid").asText("")); + elasticInfo.put("version", response.path("version").path("number").asText("")); //elasticInfo.put("elasticHost", host); //elasticInfo.put("elasticPort", String.valueOf(port)); } catch (IOException e) { @@ -80,4 +80,4 @@ public String info() { return "Failed to gather Save and Restore service info"; } } -} \ No newline at end of file +} From c82c486b85704511c665a05228baae7d47845e47 Mon Sep 17 00:00:00 2001 From: shroffk Date: Mon, 10 Aug 2026 10:34:07 -0400 Subject: [PATCH 17/17] switching all remaining elastic high level client usage to low level rest client --- .../logging/purge/ElasticIndexPurger.java | 105 +++++++++++------- .../logging/rest/AlarmLogSearchUtil.java | 57 +++------- .../alarm/logging/rest/SearchController.java | 26 ++--- .../CompositeSnapshotDataRepository.java | 1 - .../ConfigurationDataRepository.java | 1 - .../ElasticsearchTreeRepository.java | 1 - .../impl/elasticsearch/FilterRepository.java | 1 - .../elasticsearch/SnapshotDataRepository.java | 1 - 8 files changed, 91 insertions(+), 102 deletions(-) diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java index 33aee84ade..5e47c57652 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/purge/ElasticIndexPurger.java @@ -19,30 +19,25 @@ package org.phoebus.alarm.logging.purge; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.FieldSort; -import co.elastic.clients.elasticsearch._types.SortOptions; -import co.elastic.clients.elasticsearch._types.SortOrder; -import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery; -import co.elastic.clients.elasticsearch.cat.IndicesResponse; -import co.elastic.clients.elasticsearch.cat.indices.IndicesRecord; -import co.elastic.clients.elasticsearch.core.SearchRequest; -import co.elastic.clients.elasticsearch.core.SearchResponse; -import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest; -import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.ResponseException; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import org.phoebus.alarm.logging.ElasticClientHelper; -import org.phoebus.alarm.logging.rest.AlarmLogMessage; import org.phoebus.alarm.logging.rest.AlarmLogSearchUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import jakarta.annotation.PostConstruct; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -61,7 +56,7 @@ public class ElasticIndexPurger { private static final Logger logger = Logger.getLogger(ElasticIndexPurger.class.getName()); - private ElasticsearchClient elasticsearchClient; + private static final ObjectMapper objectMapper = JsonMapper.builder().build(); @SuppressWarnings("unused") @Value("${retention_period_days:0}") @@ -70,48 +65,78 @@ public class ElasticIndexPurger { @SuppressWarnings("unused") @PostConstruct public void init() { - elasticsearchClient = ElasticClientHelper.getInstance().getClient(); + // Rest5Client is obtained lazily from ElasticClientHelper when needed. } /** - * Deletes Elasticsearch indices based on the {@link AlarmLogMessage#getMessage_time()} for each index found - * by the client. The message time {@link Instant} is compared to current time minus the number of days specified as - * application property. + * Deletes Elasticsearch indices based on the last document's message_time for each alarm index found. + * Uses the low-level Rest5Client to avoid media-type header issues with ES 8 backends. */ @SuppressWarnings("unused") @Scheduled(cron = "${purge_cron_expr}") public void purgeElasticIndices() { try { - IndicesResponse indicesResponse = elasticsearchClient.cat().indices(); - List indicesRecords = indicesResponse.indices(); + Rest5Client restClient = ElasticClientHelper.getInstance().getRestClient(); + + // Get all indices via _cat/indices API (returns JSON array) + Request catRequest = new Request("GET", "/_cat/indices?format=json"); + var catResponse = restClient.performRequest(catRequest); + JsonNode indicesArray = objectMapper.readTree(catResponse.getEntity().getContent()); + Instant toInstant = Instant.now().minus(retentionPeriod, ChronoUnit.DAYS); - for (IndicesRecord indicesRecord : indicesRecords) { - // Elasticsearch may contain indices other than alarm indices... - String indexName = indicesRecord.index(); - if (indexName != null && !indexName.startsWith("_alarms") && (indexName.contains("_alarms_state") || + + if (!indicesArray.isArray()) { + logger.log(Level.WARNING, "Unexpected response from _cat/indices endpoint."); + return; + } + + for (JsonNode indexRecord : indicesArray) { + String indexName = indexRecord.path("index").asText(null); + if (indexName == null) { + continue; + } + // Only consider alarm-related indices + if (!indexName.startsWith("_alarms") && (indexName.contains("_alarms_state") || indexName.contains("_alarms_cmd") || indexName.contains("_alarms_config"))) { - // Find most recent document - based on message_time - in the alarm index. - SearchRequest searchRequest = SearchRequest.of(s -> - s.index(indexName) - .query(new MatchAllQuery.Builder().build()._toQuery()) - .size(1) - .sort(SortOptions.of(so -> so.field(FieldSort.of(f -> f.field("message_time").order(SortOrder.Desc)))))); - SearchResponse searchResponse = elasticsearchClient.search(searchRequest, AlarmLogMessage.class); - if (!searchResponse.hits().hits().isEmpty()) { - AlarmLogMessage alarmLogMessage = searchResponse.hits().hits().get(0).source(); - if (alarmLogMessage != null && alarmLogMessage.getMessage_time().isBefore(toInstant)) { - DeleteIndexRequest deleteIndexRequest = DeleteIndexRequest.of(d -> d.index(indexName)); - DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(deleteIndexRequest); - logger.log(Level.INFO, "Delete index " + indexName + " acknowledged: " + deleteIndexResponse.acknowledged()); + // Find most recent document based on message_time + String searchBody = "{\"query\":{\"match_all\":{}},\"size\":1,\"sort\":[{\"message_time\":{\"order\":\"desc\"}}]}"; + Request searchRequest = new Request("POST", + "/" + URLEncoder.encode(indexName, StandardCharsets.UTF_8) + "/_search"); + searchRequest.setJsonEntity(searchBody); + + try { + var searchResponse = restClient.performRequest(searchRequest); + JsonNode searchResult = objectMapper.readTree(searchResponse.getEntity().getContent()); + JsonNode hits = searchResult.path("hits").path("hits"); + + if (hits.isArray() && hits.size() > 0) { + JsonNode source = hits.get(0).get("_source"); + if (source != null) { + long messageTimeMillis = source.path("message_time").asLong(0L); + Instant messageInstant = Instant.ofEpochMilli(messageTimeMillis); + if (messageInstant.isBefore(toInstant)) { + Request deleteRequest = new Request("DELETE", + "/" + URLEncoder.encode(indexName, StandardCharsets.UTF_8)); + var deleteResponse = restClient.performRequest(deleteRequest); + JsonNode deleteResult = objectMapper.readTree( + deleteResponse.getEntity().getContent()); + boolean acknowledged = deleteResult.path("acknowledged").asBoolean(false); + logger.log(Level.INFO, + "Delete index " + indexName + " acknowledged: " + acknowledged); + } + } + } else { + logger.log(Level.WARNING, + "Index " + indexName + " cannot be evaluated for removal as document count is zero."); } - } else { - logger.log(Level.WARNING, "Index " + indexName + " cannot be evaluated for removal as document count is zero."); + } catch (ResponseException e) { + logger.log(Level.WARNING, "Failed to query index " + indexName + " for purge evaluation.", e); } } } } catch (IOException e) { - logger.log(Level.WARNING, "Elastic query failed", e); + logger.log(Level.WARNING, "Elastic query failed during index purge.", e); } } diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java index e16a3466e8..0d0bd2cae6 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/AlarmLogSearchUtil.java @@ -1,6 +1,5 @@ package org.phoebus.alarm.logging.rest; -import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.FieldSort; import co.elastic.clients.elasticsearch._types.SortOptions; import co.elastic.clients.elasticsearch._types.SortOrder; @@ -10,9 +9,11 @@ import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery; import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery; import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; +import jakarta.json.stream.JsonGenerator; import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; @@ -71,13 +72,11 @@ public class AlarmLogSearchUtil { /** * Find all the log (state and config) messages which match the search criteria * - * @param client elastic client * @param restClient low-level Rest5Client for performing requests * @param searchParameters search parameters * @return list of alarm state and config messages */ - public static List search(ElasticsearchClient client, - Rest5Client restClient, + public static List search(Rest5Client restClient, Map searchParameters) { logger.fine("searching for alarm log entires : " + searchParameters.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue()).collect(Collectors.joining())); @@ -293,13 +292,11 @@ public static List search(ElasticsearchClient client, /** * Return the latest alarm config message associated with 'config' * - * @param client elastic client * @param restClient low-level Rest5Client for performing requests * @param allRequestParams the wildcard pattern which matches the 'config' * @return last alarm config message for the given 'config' */ - public static List searchConfig(ElasticsearchClient client, - Rest5Client restClient, + public static List searchConfig(Rest5Client restClient, Map allRequestParams) { String configString = allRequestParams.get("config"); // Determine which alarm config to specify as Elasticsearch index, convert to lower case as @@ -390,49 +387,21 @@ public static int getDateSpanInDays(String indexDateSpanUnits){ } /** - * Helper method to convert SearchRequest to JSON string for low-level API - * This works by using Jackson to serialize the SearchRequest object tree to JSON - * NOTE: We exclude 'index' from the JSON since indices are specified in the URL path + * Helper method to convert SearchRequest to JSON string for low-level API. + * Uses JSONP serialization to produce the correct Elasticsearch request body. + * The 'index' field is NOT included because it belongs in the URL path, not the body. */ private static String buildSearchJson(SearchRequest searchRequest) throws IOException { - // Serialize the entire request first to get all fields - String fullJson = mapper.writeValueAsString(searchRequest); - JsonNode fullNode = mapper.readTree(fullJson); - - // Build a new JSON object with only the fields Elasticsearch expects in the body - Map searchBody = new java.util.LinkedHashMap<>(); - - // Query - only include if present - if (fullNode.has("query") && !fullNode.get("query").isNull()) { - searchBody.put("query", mapper.convertValue(fullNode.get("query"), Object.class)); - } - - // Size - only include if present - if (fullNode.has("size") && !fullNode.get("size").isNull()) { - searchBody.put("size", fullNode.get("size").asInt()); - } - - // Sort - only include if present - if (fullNode.has("sort") && !fullNode.get("sort").isNull() && fullNode.get("sort").isArray()) { - searchBody.put("sort", mapper.convertValue(fullNode.get("sort"), Object.class)); - } - - String result = mapper.writeValueAsString(searchBody); + java.io.StringWriter writer = new java.io.StringWriter(); + Jackson3JsonpMapper jsonpMapper = new Jackson3JsonpMapper(JsonMapper.builder().build()); + JsonGenerator generator = jsonpMapper.jsonProvider().createGenerator(writer); + searchRequest.serialize(generator, jsonpMapper); + generator.close(); + String result = writer.toString(); logger.fine("Built search JSON: " + result); return result; } - private static Object toMap(Query query) throws IOException { - // Serialize the Query object to a map via JSON round-trip - String json = mapper.writeValueAsString(query); - return mapper.readValue(json, Object.class); - } - - private static List toList(java.util.List list) throws IOException { - // Serialize the list to map via JSON round-trip - String json = mapper.writeValueAsString(list); - return mapper.readValue(json, List.class); - } private static List executeSearch(Rest5Client restClient, String endpoint, diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java index ebde1f3efe..53f26ada91 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java @@ -1,9 +1,9 @@ package org.phoebus.alarm.logging.rest; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo; -import co.elastic.clients.elasticsearch.core.InfoResponse; +import co.elastic.clients.transport.rest5_client.low_level.Request; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import tools.jackson.core.JacksonException; +import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import io.swagger.v3.oas.annotations.Operation; @@ -62,14 +62,14 @@ public String info() { Map elasticInfo = new LinkedHashMap(); try { - ElasticsearchClient client = ElasticClientHelper.getInstance().getClient(); - InfoResponse response = client.info(); - + Rest5Client restClient = ElasticClientHelper.getInstance().getRestClient(); + Request request = new Request("GET", "/"); + JsonNode responseNode = objectMapper.readTree( + restClient.performRequest(request).getEntity().getContent()); elasticInfo.put("status", "Connected"); - elasticInfo.put("clusterName", response.clusterName()); - elasticInfo.put("clusterUuid", response.clusterUuid()); - ElasticsearchVersionInfo version = response.version(); - elasticInfo.put("version", version.toString()); + elasticInfo.put("clusterName", responseNode.path("cluster_name").asText("")); + elasticInfo.put("clusterUuid", responseNode.path("cluster_uuid").asText("")); + elasticInfo.put("version", responseNode.path("version").path("number").asText("")); } catch (IOException e) { AlarmLoggingService.logger.log(Level.WARNING, "Failed to create Alarm Logging service info resource.", e); elasticInfo.put("status", "Failed to connect to elastic " + e.getLocalizedMessage()); @@ -98,7 +98,7 @@ public String info() { }) @RequestMapping(value = "/search/alarm", method = RequestMethod.GET) public List search(@Parameter(hidden = true) @RequestParam Map allRequestParams) { - List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), + List result = AlarmLogSearchUtil.search( ElasticClientHelper.getInstance().getRestClient(), allRequestParams); return result; } @@ -108,7 +108,7 @@ public List search(@Parameter(hidden = true) @RequestParam Map< public List searchPv(@Parameter(name="pv", description = "PV name") @PathVariable String pv) { Map searchParameters = new HashMap<>(); searchParameters.put("pv", pv); - List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), + List result = AlarmLogSearchUtil.search( ElasticClientHelper.getInstance().getRestClient(), searchParameters); return result; } @@ -126,7 +126,7 @@ public List searchConfig(@Parameter(hidden = true) @RequestPara allRequestParams.get("config").isEmpty()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST); } - List result = AlarmLogSearchUtil.searchConfig(ElasticClientHelper.getInstance().getClient(), + List result = AlarmLogSearchUtil.searchConfig( ElasticClientHelper.getInstance().getRestClient(), allRequestParams); return result; } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java index 4de225a7aa..c16ff81a6a 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/CompositeSnapshotDataRepository.java @@ -18,7 +18,6 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; -import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.rest5_client.low_level.Request; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java index 89c332d9f8..55d710fdaa 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepository.java @@ -18,7 +18,6 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; -import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.rest5_client.low_level.Request; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java index 486ecfd5b1..7dea6a8479 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java @@ -18,7 +18,6 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; -import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.SearchRequest; import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.rest5_client.low_level.Request; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java index 2be15898d0..d0895c20d7 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/FilterRepository.java @@ -18,7 +18,6 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; -import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java index 3f3798aa36..6f63ea7755 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/SnapshotDataRepository.java @@ -18,7 +18,6 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; -import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.ResponseException; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;