From 3ec028e14f315e532dc869ae0d25b183418b4e30 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 28 Aug 2024 11:33:40 +0200 Subject: [PATCH 01/11] Stub for replacing Jersey with native HttpClient --- .../client/SaveAndRestoreClientImpl.java | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java new file mode 100644 index 0000000000..e81a62791d --- /dev/null +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2024 European Spallation Source ERIC. + */ + +package org.phoebus.applications.saveandrestore.client; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; +import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; +import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; +import org.phoebus.applications.saveandrestore.model.Configuration; +import org.phoebus.applications.saveandrestore.model.ConfigurationData; +import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.RestoreResult; +import org.phoebus.applications.saveandrestore.model.Snapshot; +import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.applications.saveandrestore.model.Tag; +import org.phoebus.applications.saveandrestore.model.TagData; +import org.phoebus.applications.saveandrestore.model.UserData; +import org.phoebus.applications.saveandrestore.model.search.Filter; +import org.phoebus.applications.saveandrestore.model.search.SearchResult; +import org.phoebus.security.store.SecureStore; +import org.phoebus.security.tokens.AuthenticationScope; +import org.phoebus.security.tokens.ScopedAuthenticationToken; + +import javax.ws.rs.core.MultivaluedMap; +import java.io.IOException; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Base64; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class SaveAndRestoreClientImpl implements SaveAndRestoreClient{ + + private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; + private static final Logger logger = Logger.getLogger(SaveAndRestoreClientImpl.class.getName()); + + private static final int DEFAULT_READ_TIMEOUT = 5000; // ms + private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms + + private static ObjectMapper objectMapper; + + private static HttpClient client; + + static{ + int httpClientReadTimeout = Preferences.httpClientReadTimeout > 0 ? Preferences.httpClientReadTimeout : DEFAULT_READ_TIMEOUT; + logger.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms"); + + int httpClientConnectTimeout = Preferences.httpClientConnectTimeout > 0 ? Preferences.httpClientConnectTimeout : DEFAULT_CONNECT_TIMEOUT; + logger.log(Level.INFO, "Save&restore client using connect timeout " + httpClientConnectTimeout + " ms"); + CookieHandler.setDefault(new CookieManager()); + + client = HttpClient.newBuilder() + .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL)) + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(httpClientConnectTimeout)) + .build(); + objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + + private String getBasicAuthenticationHeader() { + try { + SecureStore store = new SecureStore(); + ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE); + if (scopedAuthenticationToken != null) { + String username = scopedAuthenticationToken.getUsername(); + String password = scopedAuthenticationToken.getPassword(); + return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); + } + return null; + } + + @Override + public String getServiceUrl() { + return Preferences.jmasarServiceUrl; + } + + @Override + public Node getRoot() { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/node/" + Node.ROOT_FOLDER_UNIQUE_ID)) + .GET() + .build(); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (Exception e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + public Node getNode(String uniqueNodeId) { + return null; + } + + @Override + public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { + return List.of(); + } + + @Override + public List getCompositeSnapshotItems(String uniqueNodeId) { + return List.of(); + } + + @Override + public Node getParentNode(String unqiueNodeId) { + return null; + } + + @Override + public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClientException { + return List.of(); + } + + @Override + public Node createNewNode(String parentsUniqueId, Node node) { + return null; + } + + @Override + public Node updateNode(Node nodeToUpdate) { + return null; + } + + @Override + public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { + return null; + } + + @Override + public void deleteNodes(List nodeIds) { + + } + + @Override + public List getAllTags() { + return List.of(); + } + + @Override + public List getAllSnapshots() { + return List.of(); + } + + @Override + public Node moveNodes(List sourceNodeIds, String targetNodeId) { + return null; + } + + @Override + public Node copyNodes(List sourceNodeIds, String targetNodeId) { + return null; + } + + @Override + public String getFullPath(String uniqueNodeId) { + return ""; + } + + @Override + public List getFromPath(String path) { + return List.of(); + } + + @Override + public ConfigurationData getConfigurationData(String nodeId) { + return null; + } + + @Override + public Configuration createConfiguration(String parentNodeId, Configuration configuration) { + return null; + } + + @Override + public Configuration updateConfiguration(Configuration configuration) { + return null; + } + + @Override + public SnapshotData getSnapshotData(String uniqueId) { + return null; + } + + @Override + public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { + return null; + } + + @Override + public Snapshot updateSnapshot(Snapshot snapshot) { + return null; + } + + @Override + public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { + return null; + } + + @Override + public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { + return List.of(); + } + + @Override + public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) { + return null; + } + + @Override + public SearchResult search(MultivaluedMap searchParams) { + return null; + } + + @Override + public Filter saveFilter(Filter filter) { + return null; + } + + @Override + public List getAllFilters() { + return List.of(); + } + + @Override + public void deleteFilter(String name) { + + } + + @Override + public List addTag(TagData tagData) { + return List.of(); + } + + @Override + public List deleteTag(TagData tagData) { + return List.of(); + } + + @Override + public UserData authenticate(String userName, String password) { + return null; + } + + @Override + public List restore(List snapshotItems) { + return List.of(); + } + + @Override + public List restore(String snapshotNodeId) { + return List.of(); + } + + @Override + public List takeSnapshot(String configurationNodeId) { + return List.of(); + } +} From faa41c9a25f7b3eac8ebbff6e4a3f1d062fee8c4 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Tue, 3 Sep 2024 15:39:23 +0200 Subject: [PATCH 02/11] Intermediate commit --- .../client/SaveAndRestoreClient.java | 6 +- .../client/SaveAndRestoreClientImpl.java | 98 ++++++++++++++----- .../client/SaveAndRestoreJerseyClient.java | 5 - .../ui/SaveAndRestoreService.java | 4 +- .../web/controllers/NodeController.java | 51 +++------- 5 files changed, 89 insertions(+), 75 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java index 4fdf741a7a..4d96e8e0ad 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java @@ -55,11 +55,11 @@ public interface SaveAndRestoreClient { List getCompositeSnapshotItems(String uniqueNodeId); /** - * @param unqiueNodeId Unique id of a {@link Node} + * @param uniqueNodeId Unique id of a {@link Node} * @return The parent {@link Node} of the specified id. May be null if the unique id is associated with the root * {@link Node} */ - Node getParentNode(String unqiueNodeId); + Node getParentNode(String uniqueNodeId); /** * @param uniqueNodeId Id of an existing {@link Node} @@ -132,8 +132,6 @@ public interface SaveAndRestoreClient { String getFullPath(String uniqueNodeId); - List getFromPath(String path); - ConfigurationData getConfigurationData(String nodeId); /** diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index e81a62791d..beb7cb7308 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -5,11 +5,14 @@ package org.phoebus.applications.saveandrestore.client; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; @@ -31,6 +34,8 @@ import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; @@ -41,6 +46,7 @@ import java.time.Duration; import java.util.Base64; import java.util.List; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -97,21 +103,12 @@ public String getServiceUrl() { @Override public Node getRoot() { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(Preferences.jmasarServiceUrl + "/node/" + Node.ROOT_FOLDER_UNIQUE_ID)) - .GET() - .build(); - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (Exception e) { - throw new RuntimeException(e); - } - return null; + return getNode(Node.ROOT_FOLDER_UNIQUE_ID); } @Override public Node getNode(String uniqueNodeId) { - return null; + return getCall("/node/" + uniqueNodeId, Node.class); } @Override @@ -125,13 +122,14 @@ public List getCompositeSnapshotItems(String uniqueNodeId) { } @Override - public Node getParentNode(String unqiueNodeId) { - return null; + public Node getParentNode(String uniqueNodeId) { + return getCall("/node/" + uniqueNodeId + "/parent", Node.class); } @Override public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClientException { - return List.of(); + return getCall("/node/" + uniqueNodeId + "/children", new TypeReference<>() { + }); } @Override @@ -156,12 +154,12 @@ public void deleteNodes(List nodeIds) { @Override public List getAllTags() { - return List.of(); + return getCall("/tags", new TypeReference<>(){}); } @Override public List getAllSnapshots() { - return List.of(); + return getCall("/snapshots", new TypeReference<>(){}); } @Override @@ -176,17 +174,12 @@ public Node copyNodes(List sourceNodeIds, String targetNodeId) { @Override public String getFullPath(String uniqueNodeId) { - return ""; - } - - @Override - public List getFromPath(String path) { - return List.of(); + return getCall("/path/" + uniqueNodeId, String.class); } @Override public ConfigurationData getConfigurationData(String nodeId) { - return null; + return getCall("/config/" + nodeId, ConfigurationData.class); } @Override @@ -201,7 +194,7 @@ public Configuration updateConfiguration(Configuration configuration) { @Override public SnapshotData getSnapshotData(String uniqueId) { - return null; + return getCall("/snapshot/" + uniqueId, SnapshotData.class); } @Override @@ -241,7 +234,7 @@ public Filter saveFilter(Filter filter) { @Override public List getAllFilters() { - return List.of(); + return getCall("/filters", new TypeReference<>(){}); } @Override @@ -261,7 +254,22 @@ public List deleteTag(TagData tagData) { @Override public UserData authenticate(String userName, String password) { - return null; + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(Preferences.jmasarServiceUrl) + .append("/login?username=") + .append(userName) + .append("&password=") + .append(password); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(stringBuilder.toString())) + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + return objectMapper.readValue(response.body(), UserData.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override @@ -278,4 +286,42 @@ public List restore(String snapshotNodeId) { public List takeSnapshot(String configurationNodeId) { return List.of(); } + + private T getCall(String relativeUrl, Class clazz){ + HttpResponse response = getCall(relativeUrl); + try { + return objectMapper.readValue(response.body(), clazz); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private T getCall(String relativeUrl, TypeReference typeReference){ + HttpResponse response = getCall(relativeUrl); + try { + return objectMapper.readValue(response.body(), typeReference); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private HttpResponse getCall(String relativeUrl){ + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + relativeUrl)) + .GET() + .build(); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + if(response.statusCode() != 200){ + if(responseBody == null || responseBody.isEmpty()){ + responseBody = "N/A"; + } + throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.statusCode() + ", error message: " + responseBody); + } + return response; + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java index f9b1dc332c..4fa31f2070 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java @@ -313,11 +313,6 @@ public String getFullPath(String uniqueNodeId) { return response.getEntity(String.class); } - @Override - public List getFromPath(String path) { - return null; - } - @Override public ConfigurationData getConfigurationData(String nodeId) { ClientResponse clientResponse = getCall("/config/" + nodeId); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java index 528153e75d..4fc876d404 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java @@ -19,6 +19,7 @@ package org.phoebus.applications.saveandrestore.ui; import org.epics.vtype.VType; +import org.phoebus.applications.saveandrestore.client.SaveAndRestoreClientImpl; import org.phoebus.applications.saveandrestore.model.*; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; import org.phoebus.applications.saveandrestore.model.Configuration; @@ -70,7 +71,8 @@ public class SaveAndRestoreService { private final SaveAndRestoreClient saveAndRestoreClient; private SaveAndRestoreService() { - saveAndRestoreClient = new SaveAndRestoreJerseyClient(); + //saveAndRestoreClient = new SaveAndRestoreJerseyClient(); + saveAndRestoreClient = new SaveAndRestoreClientImpl(); executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); } 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 842beae6f5..33882d3145 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 @@ -23,16 +23,18 @@ import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.security.access.expression.SecurityExpressionRoot; -import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import java.security.Principal; import java.util.List; -import java.util.logging.Logger; /** * Controller offering endpoints for CRUD operations on {@link Node}s, which represent @@ -45,7 +47,6 @@ public class NodeController extends BaseController { @Autowired private NodeDAO nodeDAO; - private final Logger logger = Logger.getLogger(NodeController.class.getName()); /** * Create a new folder in the tree structure. @@ -108,7 +109,6 @@ public List getNodes(@RequestBody List uniqueNodeIds) { } /** - * * @param uniqueNodeId Unique {@link Node} id. * @return The parent {@link Node} of #uniqueNodeId. */ @@ -119,7 +119,6 @@ public Node getParentNode(@PathVariable String uniqueNodeId) { } /** - * * @param uniqueNodeId Unique {@link Node} id. * @return Potentially empty list of child {@link Node}s of the {@link Node} identified by #uniqueNodeId. */ @@ -129,33 +128,6 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) { return nodeDAO.getChildNodes(uniqueNodeId); } - /** - * Recursively deletes a node and all its child nodes, if any. In particular, if the node id points to a configuration, - * all snapshots associated with that configuration will also be deleted. A client may wish to alert the - * user of this side effect. - *

- * A {@link HttpStatus#NOT_FOUND} is returned if the specified unique node id does not exist. - *

- *

- * A {@link HttpStatus#BAD_REQUEST} is returned if the specified unique node id is the tree root node id, - * see {@link Node#ROOT_FOLDER_UNIQUE_ID}. - *

- * - * @param uniqueNodeId The non-zero id of the node to delete - * @param authentication {@link Authentication} of authenticated user. - */ - /* - @SuppressWarnings("unused") - @DeleteMapping(value = "/node/{uniqueNodeId}", produces = JSON) - @PreAuthorize("hasRole(this.roleAdmin) or @authorizationHelper.mayDelete(#uniqueNodeId, #authentication)") - @Deprecated - public void deleteNode(@PathVariable final String uniqueNodeId, Authentication authentication) { - logger.info("Deleting node with unique id " + uniqueNodeId); - nodeDAO.deleteNode(uniqueNodeId); - } - - */ - /** * Deletes all {@link Node}s contained in the provided list. *
@@ -168,7 +140,8 @@ public void deleteNode(@PathVariable final String uniqueNodeId, Authentication a * authorities of the user. *
* Note also that an unauthenticated user (e.g. no basic authentication header in client's request) will - * receive a HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked. + * receive an HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked. + * * @param nodeIds List of {@link Node} ids to remove. */ @SuppressWarnings("unused") @@ -191,7 +164,7 @@ public void deleteNodes(@RequestBody List nodeIds) { * authorities of the user. *
* Note also that an unauthenticated user (e.g. no basic authentication header in client's request) will - * receive a HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked. + * receive an HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked. * *

* A {@link HttpStatus#BAD_REQUEST} is returned if a node of the same name and type already exists in the parent folder, @@ -213,7 +186,7 @@ public Node updateNode(@RequestParam(value = "customTimeForMigration", required throw new IllegalArgumentException("Node may not contain golden tag"); } nodeToUpdate.setUserName(principal.getName()); - return nodeDAO.updateNode(nodeToUpdate, Boolean.valueOf(customTimeForMigration)); + return nodeDAO.updateNode(nodeToUpdate, Boolean.parseBoolean(customTimeForMigration)); } /** From 317f4b14858562c2277e61ab5a1e6620cc4e3de7 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 4 Sep 2024 14:13:26 +0200 Subject: [PATCH 03/11] Pausing while investigating DELETE with body --- .../client/SaveAndRestoreClientImpl.java | 22 ++++++++++++++++--- .../web/controllers/NodeController.java | 18 ++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index beb7cb7308..ac2ac9fef8 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; @@ -113,12 +114,12 @@ public Node getNode(String uniqueNodeId) { @Override public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { - return List.of(); + return getCall("/composite-snapshot/" + uniqueNodeId + "/nodes", new TypeReference<>(){}); } @Override public List getCompositeSnapshotItems(String uniqueNodeId) { - return List.of(); + return getCall("/composite-snapshot/" + uniqueNodeId + "/items", new TypeReference<>(){}); } @Override @@ -149,7 +150,22 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { @Override public void deleteNodes(List nodeIds) { - + try { + String s = objectMapper.writeValueAsString(nodeIds); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/node/delete")) + .POST(HttpRequest.BodyPublishers.ofString(s)) + .header("Content-Type", "application/json") + .header("Authorization", getBasicAuthenticationHeader()) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + String message = response.body(); + throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.statusCode() + ", error message: " + message); + } + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override 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 33882d3145..b75eff2a0f 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 @@ -128,6 +128,19 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) { return nodeDAO.getChildNodes(uniqueNodeId); } + /** + * This is deprecated, replaced by {@link #deleteNodesAsPost(List)}. Reason for deprecation is that + * the native {@link java.net.http.HttpClient} does not support a body for DELETE requests. + * @param nodeIds + */ + @Deprecated + @SuppressWarnings("unused") + @DeleteMapping(value = "/node", produces = JSON) + @PreAuthorize("@authorizationHelper.mayDelete(#nodeIds, #root)") + public void deleteNodes(@RequestBody List nodeIds) { + deleteNodesAsPost(nodeIds); + } + /** * Deletes all {@link Node}s contained in the provided list. *
@@ -144,10 +157,9 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) { * * @param nodeIds List of {@link Node} ids to remove. */ - @SuppressWarnings("unused") - @DeleteMapping(value = "/node", produces = JSON) + @PostMapping(value = "/node/delete", produces = JSON) @PreAuthorize("@authorizationHelper.mayDelete(#nodeIds, #root)") - public void deleteNodes(@RequestBody List nodeIds) { + public void deleteNodesAsPost(@RequestBody List nodeIds) { nodeDAO.deleteNodes(nodeIds); } From e2a82523de409d8e9da8ecae22a56cbf18971155 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 5 Sep 2024 13:44:42 +0200 Subject: [PATCH 04/11] Added a few more method implementations --- .../client/SaveAndRestoreClientImpl.java | 52 +++++++++++++------ .../persistence/dao/NodeDAO.java | 1 - .../web/controllers/AuthorizationHelper.java | 24 +++++++-- .../web/controllers/NodeController.java | 35 ++++++++----- 4 files changed, 79 insertions(+), 33 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index ac2ac9fef8..1e98b9a9c7 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -14,7 +14,12 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.GenericType; +import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; import org.phoebus.applications.saveandrestore.model.Configuration; @@ -58,6 +63,7 @@ public class SaveAndRestoreClientImpl implements SaveAndRestoreClient{ private static final int DEFAULT_READ_TIMEOUT = 5000; // ms private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms + private static final Log log = LogFactory.getLog(SaveAndRestoreClientImpl.class); private static ObjectMapper objectMapper; @@ -150,22 +156,23 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { @Override public void deleteNodes(List nodeIds) { - try { - String s = objectMapper.writeValueAsString(nodeIds); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(Preferences.jmasarServiceUrl + "/node/delete")) - .POST(HttpRequest.BodyPublishers.ofString(s)) - .header("Content-Type", "application/json") - .header("Authorization", getBasicAuthenticationHeader()) - .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() != 200) { - String message = response.body(); - throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.statusCode() + ", error message: " + message); + // Native HttpClient does not support body in DELETE, so need to delete one by one... + nodeIds.forEach(id -> { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/node/" + id)) + .DELETE() + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException( "Failed to delete node " + id + ", " + response.body()); + } + } catch (Exception e) { + throw new RuntimeException(e); } - } catch (Exception e) { - throw new RuntimeException(e); - } + }); } @Override @@ -255,7 +262,20 @@ public List getAllFilters() { @Override public void deleteFilter(String name) { - + String filterName = name.replace(" ", "%20"); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/filter/" + filterName)) + .DELETE() + .header("Authorization", getBasicAuthenticationHeader()) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new RuntimeException(response.body() != null ? response.body() : Messages.deleteFilterFailed); + } + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java index 62dd0a91c9..cf4601bcbd 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/NodeDAO.java @@ -67,7 +67,6 @@ public interface NodeDAO { * * @param nodeId The unique id of the node to delete. */ - @Deprecated void deleteNode(String nodeId); /** diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java index 0a404324e3..3b4b41e0ee 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/AuthorizationHelper.java @@ -67,16 +67,32 @@ public class AuthorizationHelper { * @return true only if all if the nodes can be deleted by the user. */ public boolean mayDelete(List nodeIds, MethodSecurityExpressionOperations methodSecurityExpressionOperations) { + for (String nodeId : nodeIds) { + if (!mayDelete(nodeId, methodSecurityExpressionOperations)) { + return false; + } + } + return true; + } + + /** + * Checks if all the provided node id to this method can be deleted by the user. User with admin privileges is always + * permitted to delete, while a user not having required role may never delete. + * + * @param nodeId A {@link Node} id subject to the check. + * @param methodSecurityExpressionOperations {@link MethodSecurityExpressionOperations} Spring managed object + * queried for authorization. + * @return true only if all if the node can be deleted by the user. + */ + public boolean mayDelete(String nodeId, MethodSecurityExpressionOperations methodSecurityExpressionOperations) { if (permitAll || methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleAdmin)) { return true; } if (!methodSecurityExpressionOperations.hasAuthority(ROLE_PREFIX + roleUser)) { return false; } - for (String nodeId : nodeIds) { - if (!mayDelete(nodeId, ((UserDetails) methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername())) { - return false; - } + if (!mayDelete(nodeId, ((UserDetails) methodSecurityExpressionOperations.getAuthentication().getPrincipal()).getUsername())) { + return false; } return true; } 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 b75eff2a0f..cf385aaae4 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 @@ -129,23 +129,33 @@ public List getChildNodes(@PathVariable final String uniqueNodeId) { } /** - * This is deprecated, replaced by {@link #deleteNodesAsPost(List)}. Reason for deprecation is that - * the native {@link java.net.http.HttpClient} does not support a body for DELETE requests. - * @param nodeIds + * Deletes all {@link Node}s contained in the provided list. + *
+ * Checks are made to make sure user may delete + * the {@link Node}s, see {@link AuthorizationHelper}. If the checks fail on any of the {@link Node} ids, + * checks are aborted and client will receive an HTTP 403 response. + *
+ * Note that the {@link PreAuthorize} annotations calls a helper method in {@link AuthorizationHelper}, using + * the list of {@link Node} ids and a Spring injected object - root - used to check + * authorities of the user. + *
+ * Note also that an unauthenticated user (e.g. no basic authentication header in client's request) will + * receive an HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked. + * + * @param nodeIds List of {@link Node} ids to remove. */ - @Deprecated @SuppressWarnings("unused") @DeleteMapping(value = "/node", produces = JSON) @PreAuthorize("@authorizationHelper.mayDelete(#nodeIds, #root)") public void deleteNodes(@RequestBody List nodeIds) { - deleteNodesAsPost(nodeIds); + nodeDAO.deleteNodes(nodeIds); } /** - * Deletes all {@link Node}s contained in the provided list. + * Deletes one {@link Node}. *
* Checks are made to make sure user may delete - * the {@link Node}s, see {@link AuthorizationHelper}. If the checks fail on any of the {@link Node} ids, + * the {@link Node}, see {@link AuthorizationHelper}. If the checks fail on any of the {@link Node} ids, * checks are aborted and client will receive an HTTP 403 response. *
* Note that the {@link PreAuthorize} annotations calls a helper method in {@link AuthorizationHelper}, using @@ -155,12 +165,13 @@ public void deleteNodes(@RequestBody List nodeIds) { * Note also that an unauthenticated user (e.g. no basic authentication header in client's request) will * receive an HTTP 401 response, i.e. the {@link PreAuthorize} check is not invoked. * - * @param nodeIds List of {@link Node} ids to remove. + * @param nodeId {@link Node} id to remove. */ - @PostMapping(value = "/node/delete", produces = JSON) - @PreAuthorize("@authorizationHelper.mayDelete(#nodeIds, #root)") - public void deleteNodesAsPost(@RequestBody List nodeIds) { - nodeDAO.deleteNodes(nodeIds); + @SuppressWarnings("unused") + @DeleteMapping(value = "/node/{nodeId}", produces = JSON) + @PreAuthorize("@authorizationHelper.mayDelete(#nodeId, #root)") + public void deleteNode(@PathVariable String nodeId) { + deleteNodes(List.of(nodeId)); } /** From 4659ee83db16267bc93f5e45012af3716e3ab768 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 19 Sep 2024 11:43:36 -0400 Subject: [PATCH 05/11] Native HTTP client: create node, update node --- .../client/SaveAndRestoreClientImpl.java | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index 1e98b9a9c7..d3630a5d6a 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -6,17 +6,10 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.json.JsonReadFeature; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.GenericType; -import com.sun.jersey.api.client.UniformInterfaceException; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.phoebus.applications.saveandrestore.Messages; @@ -39,9 +32,6 @@ import org.phoebus.security.tokens.ScopedAuthenticationToken; import javax.ws.rs.core.MultivaluedMap; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; @@ -52,11 +42,10 @@ import java.time.Duration; import java.util.Base64; import java.util.List; -import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; -public class SaveAndRestoreClientImpl implements SaveAndRestoreClient{ +public class SaveAndRestoreClientImpl implements SaveAndRestoreClient { private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; private static final Logger logger = Logger.getLogger(SaveAndRestoreClientImpl.class.getName()); @@ -69,7 +58,7 @@ public class SaveAndRestoreClientImpl implements SaveAndRestoreClient{ private static HttpClient client; - static{ + static { int httpClientReadTimeout = Preferences.httpClientReadTimeout > 0 ? Preferences.httpClientReadTimeout : DEFAULT_READ_TIMEOUT; logger.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms"); @@ -120,12 +109,14 @@ public Node getNode(String uniqueNodeId) { @Override public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { - return getCall("/composite-snapshot/" + uniqueNodeId + "/nodes", new TypeReference<>(){}); + return getCall("/composite-snapshot/" + uniqueNodeId + "/nodes", new TypeReference<>() { + }); } @Override public List getCompositeSnapshotItems(String uniqueNodeId) { - return getCall("/composite-snapshot/" + uniqueNodeId + "/items", new TypeReference<>(){}); + return getCall("/composite-snapshot/" + uniqueNodeId + "/items", new TypeReference<>() { + }); } @Override @@ -141,17 +132,45 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient @Override public Node createNewNode(String parentsUniqueId, Node node) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/node?parentNodeId=" + parentsUniqueId)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(node))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Node.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public Node updateNode(Node nodeToUpdate) { - return null; + return updateNode(nodeToUpdate, false); } @Override public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/node?customTimeForMigration=" + customTimeForMigration)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(nodeToUpdate))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Node.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override @@ -167,7 +186,7 @@ public void deleteNodes(List nodeIds) { .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { - throw new SaveAndRestoreClientException( "Failed to delete node " + id + ", " + response.body()); + throw new SaveAndRestoreClientException("Failed to delete node " + id + ", " + response.body()); } } catch (Exception e) { throw new RuntimeException(e); @@ -177,12 +196,14 @@ public void deleteNodes(List nodeIds) { @Override public List getAllTags() { - return getCall("/tags", new TypeReference<>(){}); + return getCall("/tags", new TypeReference<>() { + }); } @Override public List getAllSnapshots() { - return getCall("/snapshots", new TypeReference<>(){}); + return getCall("/snapshots", new TypeReference<>() { + }); } @Override @@ -257,7 +278,8 @@ public Filter saveFilter(Filter filter) { @Override public List getAllFilters() { - return getCall("/filters", new TypeReference<>(){}); + return getCall("/filters", new TypeReference<>() { + }); } @Override @@ -323,7 +345,7 @@ public List takeSnapshot(String configurationNodeId) { return List.of(); } - private T getCall(String relativeUrl, Class clazz){ + private T getCall(String relativeUrl, Class clazz) { HttpResponse response = getCall(relativeUrl); try { return objectMapper.readValue(response.body(), clazz); @@ -332,7 +354,7 @@ private T getCall(String relativeUrl, Class clazz){ } } - private T getCall(String relativeUrl, TypeReference typeReference){ + private T getCall(String relativeUrl, TypeReference typeReference) { HttpResponse response = getCall(relativeUrl); try { return objectMapper.readValue(response.body(), typeReference); @@ -341,7 +363,7 @@ private T getCall(String relativeUrl, TypeReference typeReference){ } } - private HttpResponse getCall(String relativeUrl){ + private HttpResponse getCall(String relativeUrl) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(Preferences.jmasarServiceUrl + relativeUrl)) .GET() @@ -349,8 +371,8 @@ private HttpResponse getCall(String relativeUrl){ try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); String responseBody = response.body(); - if(response.statusCode() != 200){ - if(responseBody == null || responseBody.isEmpty()){ + if (response.statusCode() != 200) { + if (responseBody == null || responseBody.isEmpty()) { responseBody = "N/A"; } throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.statusCode() + ", error message: " + responseBody); From 420116e476c8cbaef479b9803a4e675103360cc2 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 19 Sep 2024 11:59:21 -0400 Subject: [PATCH 06/11] Native HTTP client: move nodes --- .../client/SaveAndRestoreClientImpl.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index d3630a5d6a..9da9acecef 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -10,6 +10,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.phoebus.applications.saveandrestore.Messages; @@ -32,6 +34,7 @@ import org.phoebus.security.tokens.ScopedAuthenticationToken; import javax.ws.rs.core.MultivaluedMap; +import java.io.IOException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; @@ -208,7 +211,22 @@ public List getAllSnapshots() { @Override public Node moveNodes(List sourceNodeIds, String targetNodeId) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/move?to=" + targetNodeId)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(sourceNodeIds))) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Node.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override From 90e583219bf499dc2f713ce847a3676a19d3c7bf Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 25 Sep 2024 16:33:01 +0200 Subject: [PATCH 07/11] A few more native HttpClient implementations for save&restore --- .../client/SaveAndRestoreClientImpl.java | 119 ++++++++++++++++-- 1 file changed, 112 insertions(+), 7 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index 9da9acecef..292aee4024 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -11,6 +11,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.GenericType; +import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -231,7 +233,22 @@ public Node moveNodes(List sourceNodeIds, String targetNodeId) { @Override public Node copyNodes(List sourceNodeIds, String targetNodeId) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/copy?to=" + targetNodeId)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(sourceNodeIds))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Node.class); + } + catch(Exception e){ + throw new RuntimeException(e); + } } @Override @@ -246,12 +263,44 @@ public ConfigurationData getConfigurationData(String nodeId) { @Override public Configuration createConfiguration(String parentNodeId, Configuration configuration) { - return null; + + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/config?parentNodeId=" + parentNodeId)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(configuration))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Configuration.class); + } + catch(Exception e){ + throw new RuntimeException(e); + } } @Override public Configuration updateConfiguration(Configuration configuration) { - return null; + + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/config")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(configuration))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Configuration.class); + } + catch(Exception e){ + throw new RuntimeException(e); + } } @Override @@ -261,22 +310,78 @@ public SnapshotData getSnapshotData(String uniqueId) { @Override public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/snapshot?parentNodeId=" + parentNodeId)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(snapshot))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Snapshot.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public Snapshot updateSnapshot(Snapshot snapshot) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/snapshot")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(snapshot))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), Snapshot.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/composite-snapshot?parentNodeId=" + parentNodeId)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(compositeSnapshot))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), CompositeSnapshot.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { - return List.of(); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/composite-snapshot-consistency-check")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(snapshotNodeIds))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return objectMapper.readValue(response.body(), new TypeReference<>() {}); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override From 19e17c7566d3dce5977fc8a5ddf84fa4d4f8e137 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 26 Sep 2024 11:20:22 +0200 Subject: [PATCH 08/11] Final implementations for HttpClient in save&restore --- .../client/SaveAndRestoreClient.java | 15 +- .../client/SaveAndRestoreClientImpl.java | 365 ++++++++++++++---- .../client/SaveAndRestoreJerseyClient.java | 46 ++- .../ui/ContextMenuSnapshot.java | 1 + .../web/controllers/TagController.java | 25 +- 5 files changed, 347 insertions(+), 105 deletions(-) diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java index 4d96e8e0ad..cc81f3c8d8 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java @@ -107,10 +107,6 @@ public interface SaveAndRestoreClient { */ List getAllTags(); - /** - * @return All snapshot {@link Node}s persisted on the remote service - */ - List getAllSnapshots(); /** * Move a set of {@link Node}s to a new parent {@link Node} @@ -130,6 +126,11 @@ public interface SaveAndRestoreClient { */ Node copyNodes(List sourceNodeIds, String targetNodeId); + /** + * Constructs a path like string to facilitate location of a {@link Node} in the tree structure. + * @param uniqueNodeId Unique id + * @return Path like /Root folder/foo/bar/my/favourite/node + */ String getFullPath(String uniqueNodeId); ConfigurationData getConfigurationData(String nodeId); @@ -149,6 +150,12 @@ public interface SaveAndRestoreClient { SnapshotData getSnapshotData(String uniqueId); + /** + * Creates a {@link Snapshot} + * @param parentNodeId The unique id of the configuration {@link Node} associated with the {@link Snapshot} + * @param snapshot The {@link Snapshot} data object. + * @return The new {@link Snapshot} as persisted by the service + */ Snapshot createSnapshot(String parentNodeId, Snapshot snapshot); Snapshot updateSnapshot(Snapshot snapshot); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index 292aee4024..c69f97aa84 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -10,12 +10,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.GenericType; -import com.sun.jersey.api.client.UniformInterfaceException; -import com.sun.jersey.api.client.WebResource; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; @@ -36,7 +30,6 @@ import org.phoebus.security.tokens.ScopedAuthenticationToken; import javax.ws.rs.core.MultivaluedMap; -import java.io.IOException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; @@ -53,33 +46,32 @@ public class SaveAndRestoreClientImpl implements SaveAndRestoreClient { private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; - private static final Logger logger = Logger.getLogger(SaveAndRestoreClientImpl.class.getName()); + private static final Logger LOGGER = Logger.getLogger(SaveAndRestoreClientImpl.class.getName()); private static final int DEFAULT_READ_TIMEOUT = 5000; // ms private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms - private static final Log log = LogFactory.getLog(SaveAndRestoreClientImpl.class); - private static ObjectMapper objectMapper; + private static final ObjectMapper OBJECT_MAPPER; - private static HttpClient client; + private static final HttpClient CLIENT; static { int httpClientReadTimeout = Preferences.httpClientReadTimeout > 0 ? Preferences.httpClientReadTimeout : DEFAULT_READ_TIMEOUT; - logger.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms"); + LOGGER.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms"); int httpClientConnectTimeout = Preferences.httpClientConnectTimeout > 0 ? Preferences.httpClientConnectTimeout : DEFAULT_CONNECT_TIMEOUT; - logger.log(Level.INFO, "Save&restore client using connect timeout " + httpClientConnectTimeout + " ms"); + LOGGER.log(Level.INFO, "Save&restore client using connect timeout " + httpClientConnectTimeout + " ms"); CookieHandler.setDefault(new CookieManager()); - client = HttpClient.newBuilder() + CLIENT = HttpClient.newBuilder() .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL)) .followRedirects(HttpClient.Redirect.NORMAL) .connectTimeout(Duration.ofMillis(httpClientConnectTimeout)) .build(); - objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.registerModule(new JavaTimeModule()); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + OBJECT_MAPPER.registerModule(new JavaTimeModule()); + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); } private String getBasicAuthenticationHeader() { @@ -92,7 +84,7 @@ private String getBasicAuthenticationHeader() { return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); } } catch (Exception e) { - logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); + LOGGER.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); } return null; } @@ -135,6 +127,12 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient }); } + /** + * {@inheritDoc} + * @param parentsUniqueId Unique id of the parent {@link Node} for the new {@link Node} + * @param node A {@link Node} object that should be created (=persisted). + * @return {@inheritDoc} + */ @Override public Node createNewNode(String parentsUniqueId, Node node) { try { @@ -142,13 +140,13 @@ public Node createNewNode(String parentsUniqueId, Node node) { .uri(URI.create(Preferences.jmasarServiceUrl + "/node?parentNodeId=" + parentsUniqueId)) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(node))) + .PUT(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(node))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Node.class); + return OBJECT_MAPPER.readValue(response.body(), Node.class); } catch (Exception e) { throw new RuntimeException(e); } @@ -166,18 +164,22 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { .uri(URI.create(Preferences.jmasarServiceUrl + "/node?customTimeForMigration=" + customTimeForMigration)) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(nodeToUpdate))) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(nodeToUpdate))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Node.class); + return OBJECT_MAPPER.readValue(response.body(), Node.class); } catch (Exception e) { throw new RuntimeException(e); } } + /** + * {@inheritDoc} + * @param nodeIds List of unique {@link Node} ids. + */ @Override public void deleteNodes(List nodeIds) { // Native HttpClient does not support body in DELETE, so need to delete one by one... @@ -189,7 +191,7 @@ public void deleteNodes(List nodeIds) { .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException("Failed to delete node " + id + ", " + response.body()); } @@ -199,18 +201,22 @@ public void deleteNodes(List nodeIds) { }); } + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ @Override public List getAllTags() { return getCall("/tags", new TypeReference<>() { }); } - @Override - public List getAllSnapshots() { - return getCall("/snapshots", new TypeReference<>() { - }); - } - + /** + * {@inheritDoc} + * @param sourceNodeIds List of unique {@link Node} ids. + * @param targetNodeId The unique id of the parent {@link Node} to which the source {@link Node}s are moved. + * @return + */ @Override public Node moveNodes(List sourceNodeIds, String targetNodeId) { try { @@ -218,19 +224,26 @@ public Node moveNodes(List sourceNodeIds, String targetNodeId) { .uri(URI.create(Preferences.jmasarServiceUrl + "/move?to=" + targetNodeId)) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(sourceNodeIds))) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(sourceNodeIds))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Node.class); + return OBJECT_MAPPER.readValue(response.body(), Node.class); } catch (Exception e) { throw new RuntimeException(e); } } + /** + * {@inheritDoc} + * + * @param sourceNodeIds List of unique {@link Node} ids. + * @param targetNodeId The unique id of the parent {@link Node} to which the source {@link Node}s are copied. + * @return + */ @Override public Node copyNodes(List sourceNodeIds, String targetNodeId) { try { @@ -238,19 +251,24 @@ public Node copyNodes(List sourceNodeIds, String targetNodeId) { .uri(URI.create(Preferences.jmasarServiceUrl + "/copy?to=" + targetNodeId)) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(sourceNodeIds))) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(sourceNodeIds))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Node.class); - } - catch(Exception e){ + return OBJECT_MAPPER.readValue(response.body(), Node.class); + } catch (Exception e) { throw new RuntimeException(e); } } + /** + * {@inheritDoc} + * + * @param uniqueNodeId Unique id + * @return + */ @Override public String getFullPath(String uniqueNodeId) { return getCall("/path/" + uniqueNodeId, String.class); @@ -261,27 +279,37 @@ public ConfigurationData getConfigurationData(String nodeId) { return getCall("/config/" + nodeId, ConfigurationData.class); } + /** + * {@inheritDoc} + * @param parentNodeId Non-null and non-empty unique id of an existing parent {@link Node}, + * which must be of type {@link org.phoebus.applications.saveandrestore.model.NodeType#FOLDER}. + * @param configuration {@link ConfigurationData} object + * @return + */ @Override public Configuration createConfiguration(String parentNodeId, Configuration configuration) { - try { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(Preferences.jmasarServiceUrl + "/config?parentNodeId=" + parentNodeId)) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(configuration))) + .PUT(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(configuration))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Configuration.class); - } - catch(Exception e){ + return OBJECT_MAPPER.readValue(response.body(), Configuration.class); + } catch (Exception e) { throw new RuntimeException(e); } } + /** + * {@inheritDoc} + * @param configuration + * @return + */ @Override public Configuration updateConfiguration(Configuration configuration) { @@ -290,15 +318,14 @@ public Configuration updateConfiguration(Configuration configuration) { .uri(URI.create(Preferences.jmasarServiceUrl + "/config")) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(configuration))) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(configuration))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Configuration.class); - } - catch(Exception e){ + return OBJECT_MAPPER.readValue(response.body(), Configuration.class); + } catch (Exception e) { throw new RuntimeException(e); } } @@ -308,6 +335,12 @@ public SnapshotData getSnapshotData(String uniqueId) { return getCall("/snapshot/" + uniqueId, SnapshotData.class); } + /** + * {@inheritDoc} + * @param parentNodeId The unique id of the configuration {@link Node} associated with the {@link Snapshot} + * @param snapshot The {@link Snapshot} data object. + * @return {@inheritDoc} + */ @Override public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { try { @@ -315,13 +348,13 @@ public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { .uri(URI.create(Preferences.jmasarServiceUrl + "/snapshot?parentNodeId=" + parentNodeId)) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(snapshot))) + .PUT(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(snapshot))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Snapshot.class); + return OBJECT_MAPPER.readValue(response.body(), Snapshot.class); } catch (Exception e) { throw new RuntimeException(e); } @@ -334,18 +367,24 @@ public Snapshot updateSnapshot(Snapshot snapshot) { .uri(URI.create(Preferences.jmasarServiceUrl + "/snapshot")) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(snapshot))) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(snapshot))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), Snapshot.class); + return OBJECT_MAPPER.readValue(response.body(), Snapshot.class); } catch (Exception e) { throw new RuntimeException(e); } } + /** + * {@inheritDoc} + * @param parentNodeId The parent {@link Node} for the new {@link CompositeSnapshot} + * @param compositeSnapshot The data object + * @return A {@link CompositeSnapshot} as persisted by the service. + */ @Override public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { try { @@ -353,18 +392,26 @@ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeS .uri(URI.create(Preferences.jmasarServiceUrl + "/composite-snapshot?parentNodeId=" + parentNodeId)) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .PUT(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(compositeSnapshot))) + .PUT(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(compositeSnapshot))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), CompositeSnapshot.class); + return OBJECT_MAPPER.readValue(response.body(), CompositeSnapshot.class); } catch (Exception e) { throw new RuntimeException(e); } } + /** + * {@inheritDoc} + * + * @param snapshotNodeIds List of {@link Node} ids corresponding to {@link Node}s of types + * {@link org.phoebus.applications.saveandrestore.model.NodeType#SNAPSHOT} + * and {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT} + * @return + */ @Override public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { try { @@ -372,13 +419,14 @@ public List checkCompositeSnapshotConsistency(List snapshotNodeI .uri(URI.create(Preferences.jmasarServiceUrl + "/composite-snapshot-consistency-check")) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(snapshotNodeIds))) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(snapshotNodeIds))) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new SaveAndRestoreClientException(response.body()); } - return objectMapper.readValue(response.body(), new TypeReference<>() {}); + return OBJECT_MAPPER.readValue(response.body(), new TypeReference<>() { + }); } catch (Exception e) { throw new RuntimeException(e); } @@ -386,25 +434,73 @@ public List checkCompositeSnapshotConsistency(List snapshotNodeI @Override public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/composite-snapshot")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(compositeSnapshot))) + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return OBJECT_MAPPER.readValue(response.body(), CompositeSnapshot.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public SearchResult search(MultivaluedMap searchParams) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/search?" + mapToQueryParams(searchParams))) + .header("Content-Type", CONTENT_TYPE_JSON) + .GET() + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return OBJECT_MAPPER.readValue(response.body(), SearchResult.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public Filter saveFilter(Filter filter) { - return null; + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/filter")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .PUT(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(filter))) + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return OBJECT_MAPPER.readValue(response.body(), Filter.class); + } catch (Exception e) { + throw new RuntimeException(e); + } } + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ @Override public List getAllFilters() { return getCall("/filters", new TypeReference<>() { }); } + /** + * {@inheritDoc} + */ @Override public void deleteFilter(String name) { String filterName = name.replace(" ", "%20"); @@ -414,7 +510,7 @@ public void deleteFilter(String name) { .DELETE() .header("Authorization", getBasicAuthenticationHeader()) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new RuntimeException(response.body() != null ? response.body() : Messages.deleteFilterFailed); } @@ -423,55 +519,143 @@ public void deleteFilter(String name) { } } + /** + * {@inheritDoc} + * + * @param tagData see {@link TagData} + * @return + */ @Override public List addTag(TagData tagData) { - return List.of(); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/tags")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(tagData))) + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return OBJECT_MAPPER.readValue(response.body(), new TypeReference<>() { + }); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override public List deleteTag(TagData tagData) { - return List.of(); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/delete-tags")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(tagData))) + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return OBJECT_MAPPER.readValue(response.body(), new TypeReference<>() { + }); + } catch (Exception e) { + throw new RuntimeException(e); + } } + /** + * {@inheritDoc} + * + * @param userName User's account name + * @param password User's password + * @return {@inheritDoc} + */ @Override public UserData authenticate(String userName, String password) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(Preferences.jmasarServiceUrl) - .append("/login?username=") - .append(userName) - .append("&password=") - .append(password); + String stringBuilder = Preferences.jmasarServiceUrl + + "/login?username=" + + userName + + "&password=" + + password; HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(stringBuilder.toString())) + .uri(URI.create(stringBuilder)) .POST(HttpRequest.BodyPublishers.noBody()) .build(); try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - return objectMapper.readValue(response.body(), UserData.class); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + return OBJECT_MAPPER.readValue(response.body(), UserData.class); } catch (Exception e) { throw new RuntimeException(e); } } + /** + * {@inheritDoc} + * @param snapshotItems A {@link List} of {@link SnapshotItem}s + * @return {@inheritDoc} + */ @Override public List restore(List snapshotItems) { - return List.of(); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/restore/items")) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(snapshotItems))) + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return OBJECT_MAPPER.readValue(response.body(), new TypeReference<>() { + }); + } catch (Exception e) { + throw new RuntimeException(e); + } } + /** + * {@inheritDoc} + * @param snapshotNodeId Unique id of a snapshot + * @return {@inheritDoc} + */ @Override public List restore(String snapshotNodeId) { - return List.of(); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/restore/node?nodeId=" + snapshotNodeId)) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException(response.body()); + } + return OBJECT_MAPPER.readValue(response.body(), new TypeReference<>() { + }); + } catch (Exception e) { + throw new RuntimeException(e); + } } + /** + * {@inheritDoc} + * @param configurationNodeId The unique id of the {@link Configuration} for which to take the snapshot + * @return {@inheritDoc} + */ @Override public List takeSnapshot(String configurationNodeId) { - return List.of(); + return getCall("/take-snapshot/" + configurationNodeId, new TypeReference<>() { + }); } private T getCall(String relativeUrl, Class clazz) { HttpResponse response = getCall(relativeUrl); try { - return objectMapper.readValue(response.body(), clazz); + return OBJECT_MAPPER.readValue(response.body(), clazz); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -480,7 +664,7 @@ private T getCall(String relativeUrl, Class clazz) { private T getCall(String relativeUrl, TypeReference typeReference) { HttpResponse response = getCall(relativeUrl); try { - return objectMapper.readValue(response.body(), typeReference); + return OBJECT_MAPPER.readValue(response.body(), typeReference); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -492,7 +676,7 @@ private HttpResponse getCall(String relativeUrl) { .GET() .build(); try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); String responseBody = response.body(); if (response.statusCode() != 200) { if (responseBody == null || responseBody.isEmpty()) { @@ -505,4 +689,17 @@ private HttpResponse getCall(String relativeUrl) { throw new RuntimeException(e); } } + + private String mapToQueryParams(MultivaluedMap map) { + StringBuilder stringBuilder = new StringBuilder(); + map.keySet().forEach(k -> { + List value = map.get(k); + if (value != null && !value.isEmpty()) { + stringBuilder.append(k).append("="); + stringBuilder.append(String.join(",", value)); + stringBuilder.append("&"); + } + }); + return stringBuilder.toString(); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java index 4fa31f2070..863115494f 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java @@ -22,13 +22,27 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; -import com.sun.jersey.api.client.*; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.GenericType; +import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; -import org.phoebus.applications.saveandrestore.model.*; +import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; +import org.phoebus.applications.saveandrestore.model.Configuration; +import org.phoebus.applications.saveandrestore.model.ConfigurationData; +import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.RestoreResult; +import org.phoebus.applications.saveandrestore.model.Snapshot; +import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.applications.saveandrestore.model.Tag; +import org.phoebus.applications.saveandrestore.model.TagData; +import org.phoebus.applications.saveandrestore.model.UserData; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.applications.saveandrestore.model.search.SearchResult; import org.phoebus.security.store.SecureStore; @@ -80,7 +94,7 @@ public SaveAndRestoreJerseyClient() { mapper.setSerializationInclusion(Include.NON_NULL); } - private Client getClient(){ + private Client getClient() { try { SecureStore store = new SecureStore(); ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE); @@ -250,13 +264,6 @@ public List getAllTags() { }); } - @Override - public List getAllSnapshots() { - ClientResponse response = getCall("/snapshots"); - return response.getEntity(new GenericType<>() { - }); - } - @Override public Node moveNodes(List sourceNodeIds, String targetNodeId) { WebResource webResource = @@ -413,7 +420,12 @@ public Snapshot updateSnapshot(Snapshot snapshot) { return response.getEntity(Snapshot.class); } - + /** + * {@inheritDoc} + * @param parentNodeId The parent {@link Node} for the new {@link CompositeSnapshot} + * @param compositeSnapshot The data object + * @return + */ @Override public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { WebResource webResource = @@ -434,6 +446,12 @@ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeS return response.getEntity(CompositeSnapshot.class); } + /** + * @param snapshotNodeIds List of {@link Node} ids corresponding to {@link Node}s of types + * {@link org.phoebus.applications.saveandrestore.model.NodeType#SNAPSHOT} + * and {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT} + * @return + */ @Override public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { WebResource webResource = @@ -635,7 +653,7 @@ public UserData authenticate(String userName, String password) { } @Override - public List restore(List snapshotItems){ + public List restore(List snapshotItems) { WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/restore/items"); ClientResponse response; @@ -659,7 +677,7 @@ public List restore(List snapshotItems){ }); } - public List restore(String snapshotNodeId){ + public List restore(String snapshotNodeId) { WebResource webResource = getClient() .resource(Preferences.jmasarServiceUrl + "/restore/node") @@ -685,7 +703,7 @@ public List restore(String snapshotNodeId){ } @Override - public List takeSnapshot(String configNodeId){ + public List takeSnapshot(String configNodeId) { WebResource webResource = getClient() .resource(Preferences.jmasarServiceUrl + "/take-snapshot/" + configNodeId); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java index 324e05531d..f2ba616d58 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java @@ -118,5 +118,6 @@ protected void runChecks() { mayTagProperty.set(saveAndRestoreController.checkTaggable()); mayCompareSnapshotsProperty.set(saveAndRestoreController.compareSnapshotsPossible()); mayTagOrUntagGoldenProperty.set(saveAndRestoreController.configureGoldenItem(tagGoldenMenuItem)); + mayCopyProperty.set(saveAndRestoreController.mayCopy()); } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java index 7ca04e5036..a653943470 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java @@ -27,7 +27,11 @@ import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; import java.security.Principal; import java.util.List; @@ -46,7 +50,6 @@ public class TagController extends BaseController { private NodeDAO nodeDAO; /** - * * @return A {@link List} of all {@link Tag}s. */ @GetMapping("/tags") @@ -72,7 +75,7 @@ public List addTag(@RequestBody TagData tagData, /** * Removes a {@link Tag} from specified list of target {@link Node}s. The {@link Tag} contained - * * in tagData must be non-null, and its name must be non-null and non-empty. + * in tagData must be non-null, and its name must be non-null and non-empty. * * @param tagData See {@link TagData} * @return The list of updated {@link Node}s @@ -82,4 +85,20 @@ public List addTag(@RequestBody TagData tagData, public List deleteTag(@RequestBody TagData tagData) { return nodeDAO.deleteTag(tagData); } + + /** + * Removes a {@link Tag} from specified list of target {@link Node}s. The {@link Tag} contained + * in tagData must be non-null, and its name must be non-null and non-empty. + *

+ * This is exposed as an HTTP POST as the native {@link java.net.http.HttpClient} does not + * support a body in a DELETE request. + * + * @param tagData See {@link TagData} + * @return The list of updated {@link Node}s + */ + @PostMapping("/delete-tags") + @PreAuthorize("@authorizationHelper.mayAddOrDeleteTag(#tagData, #root)") + public List deleteTagsAsPost(@RequestBody TagData tagData) { + return nodeDAO.deleteTag(tagData); + } } From fe4775ca3ecb63d275627d5f5b0c9ecf1c45e0c9 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 26 Sep 2024 14:17:16 +0200 Subject: [PATCH 09/11] Added integration tests for the client --- app/save-and-restore/app/pom.xml | 23 + .../client/HttpClientTestIT.java | 446 ++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/client/HttpClientTestIT.java diff --git a/app/save-and-restore/app/pom.xml b/app/save-and-restore/app/pom.xml index bc4243c848..f0dfac98bd 100644 --- a/app/save-and-restore/app/pom.xml +++ b/app/save-and-restore/app/pom.xml @@ -9,6 +9,10 @@ save-and-restore + + true + + @@ -89,5 +93,24 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.2.5 + + ${skipITs} + + **/*IT.java + + + + diff --git a/app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/client/HttpClientTestIT.java b/app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/client/HttpClientTestIT.java new file mode 100644 index 0000000000..d690411e98 --- /dev/null +++ b/app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/client/HttpClientTestIT.java @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2024 European Spallation Source ERIC. + */ + +package org.phoebus.applications.saveandrestore.client; + +import org.epics.vtype.Alarm; +import org.epics.vtype.Display; +import org.epics.vtype.Time; +import org.epics.vtype.VInt; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; +import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; +import org.phoebus.applications.saveandrestore.model.ConfigPv; +import org.phoebus.applications.saveandrestore.model.Configuration; +import org.phoebus.applications.saveandrestore.model.ConfigurationData; +import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.NodeType; +import org.phoebus.applications.saveandrestore.model.RestoreResult; +import org.phoebus.applications.saveandrestore.model.Snapshot; +import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.applications.saveandrestore.model.Tag; +import org.phoebus.applications.saveandrestore.model.TagData; +import org.phoebus.applications.saveandrestore.model.search.Filter; +import org.phoebus.applications.saveandrestore.model.search.SearchResult; +import org.phoebus.security.PhoebusSecurity; +import org.phoebus.security.store.SecureStore; +import org.phoebus.security.store.SecureStoreTarget; +import org.phoebus.security.tokens.AuthenticationScope; +import org.phoebus.security.tokens.ScopedAuthenticationToken; + +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Integration test to verify that save&restore client supports all + * calls to the service. + * + * To run it, the save-and-restore service must be up and running on localhost port 8080, + * and must apply the in-memory (hard-coded) authentication scheme. + * + * Disable by default, to enable: + * + * mvn -DskipITs=false ... + */ +public class HttpClientTestIT { + + private static SaveAndRestoreClient saveAndRestoreClient; + private static Node topLevelTestNode; + + @BeforeAll + public static void init() { + PhoebusSecurity.secure_store_target = SecureStoreTarget.IN_MEMORY; + try { + SecureStore store = new SecureStore(); + ScopedAuthenticationToken scopedAuthenticationToken = + new ScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE, "admin", "adminPass"); + store.setScopedAuthentication(scopedAuthenticationToken); + } catch (Exception e) { + throw new RuntimeException(e); + } + saveAndRestoreClient = new SaveAndRestoreClientImpl(); + Node node = Node.builder() + .name("IT test folder") + .build(); + Node rootNode = saveAndRestoreClient.getRoot(); + List nodes = saveAndRestoreClient.getChildNodes(rootNode.getUniqueId()); + + nodes.stream().forEach(n -> { + if (n.getName().equals("IT test folder")) { + saveAndRestoreClient.deleteNodes(List.of(n.getUniqueId())); + } + }); + + topLevelTestNode = saveAndRestoreClient.createNewNode(rootNode.getUniqueId(), node); + } + + @AfterAll + public static void cleanUp() { + saveAndRestoreClient.deleteNodes(List.of(topLevelTestNode.getUniqueId())); + } + + @Test + public void testAuthenticate() { + saveAndRestoreClient.authenticate("admin", "adminPass"); + } + + @Test + public void testCreateFoldersAndMove() { + Node node = Node.builder() + .name("Folder 1") + .build(); + node = saveAndRestoreClient.createNewNode(topLevelTestNode.getUniqueId(), node); + + Node node2 = Node.builder() + .name("Folder 2") + .build(); + node2 = saveAndRestoreClient.createNewNode(topLevelTestNode.getUniqueId(), node2); + + saveAndRestoreClient.moveNodes(List.of(node2.getUniqueId()), node.getUniqueId()); + saveAndRestoreClient.deleteNodes(List.of(node.getUniqueId())); + } + + @Test + public void testCreateAndUpdateConfiguration() { + Node node = Node.builder() + .nodeType(NodeType.CONFIGURATION) + .name("config") + .description("description") + .build(); + + ConfigurationData configurationData = new ConfigurationData(); + ConfigPv configPv = ConfigPv.builder() + .pvName("pv") + .readbackPvName("readback") + .build(); + configurationData.setPvList(List.of(configPv)); + Configuration configuration = new Configuration(); + configuration.setConfigurationData(configurationData); + configuration.setConfigurationNode(node); + + configuration = saveAndRestoreClient.createConfiguration(topLevelTestNode.getUniqueId(), configuration); + + node = configuration.getConfigurationNode(); + node.setName("another"); + node.setDescription("Foo"); + + configuration = saveAndRestoreClient.updateConfiguration(configuration); + assertEquals("another", configuration.getConfigurationNode().getName()); + + saveAndRestoreClient.deleteNodes(List.of(configuration.getConfigurationNode().getUniqueId())); + } + + @Test + public void testCreateTagAndUpdateSnapshot() { + Node node = Node.builder() + .nodeType(NodeType.CONFIGURATION) + .name("config") + .description("description") + .build(); + + ConfigurationData configurationData = new ConfigurationData(); + ConfigPv configPv = ConfigPv.builder() + .pvName("pv") + .readbackPvName("readback") + .build(); + configurationData.setPvList(List.of(configPv)); + Configuration configuration = new Configuration(); + configuration.setConfigurationData(configurationData); + configuration.setConfigurationNode(node); + + configuration = saveAndRestoreClient.createConfiguration(topLevelTestNode.getUniqueId(), configuration); + + SnapshotItem snapshotItem = new SnapshotItem(); + snapshotItem.setConfigPv(configPv); + snapshotItem.setValue(VInt.of(777, Alarm.none(), Time.now(), Display.none())); + snapshotItem.setReadbackValue(VInt.of(42, Alarm.none(), Time.now(), Display.none())); + + SnapshotData snapshotData = new SnapshotData(); + snapshotData.setSnapshotItems(List.of(snapshotItem)); + + Node snapshotNode = Node.builder() + .nodeType(NodeType.SNAPSHOT) + .name("snapshot") + .description("description") + .build(); + Snapshot snapshot = new Snapshot(); + snapshot.setSnapshotData(snapshotData); + snapshot.setSnapshotNode(snapshotNode); + + snapshot = saveAndRestoreClient.createSnapshot(configuration.getConfigurationNode().getUniqueId(), + snapshot); + + snapshotNode = snapshot.getSnapshotNode(); + snapshotNode.setName("another"); + + snapshot = saveAndRestoreClient.updateSnapshot(snapshot); + + assertEquals("another", snapshot.getSnapshotNode().getName()); + + saveAndRestoreClient.deleteNodes(List.of(configuration.getConfigurationNode().getUniqueId())); + } + + @Test + public void testTaggingAndDeleteTags() { + Node node = Node.builder() + .nodeType(NodeType.CONFIGURATION) + .name("config") + .description("description") + .build(); + + ConfigurationData configurationData = new ConfigurationData(); + ConfigPv configPv = ConfigPv.builder() + .pvName("pv") + .readbackPvName("readback") + .build(); + configurationData.setPvList(List.of(configPv)); + Configuration configuration = new Configuration(); + configuration.setConfigurationData(configurationData); + configuration.setConfigurationNode(node); + + configuration = saveAndRestoreClient.createConfiguration(topLevelTestNode.getUniqueId(), configuration); + + SnapshotItem snapshotItem = new SnapshotItem(); + snapshotItem.setConfigPv(configPv); + snapshotItem.setValue(VInt.of(777, Alarm.none(), Time.now(), Display.none())); + snapshotItem.setReadbackValue(VInt.of(42, Alarm.none(), Time.now(), Display.none())); + + SnapshotData snapshotData = new SnapshotData(); + snapshotData.setSnapshotItems(List.of(snapshotItem)); + + Node snapshotNode = Node.builder() + .nodeType(NodeType.SNAPSHOT) + .name("snapshot") + .description("description") + .build(); + Snapshot snapshot = new Snapshot(); + snapshot.setSnapshotData(snapshotData); + snapshot.setSnapshotNode(snapshotNode); + + snapshot = saveAndRestoreClient.createSnapshot(configuration.getConfigurationNode().getUniqueId(), + snapshot); + + List tags = saveAndRestoreClient.getAllTags(); + int countBefore = tags.size(); + + TagData tagData = new TagData(); + tagData.setUniqueNodeIds(List.of(snapshot.getSnapshotNode().getUniqueId())); + tagData.setTag(Tag.goldenTag("admin")); + + saveAndRestoreClient.addTag(tagData); + + tags = saveAndRestoreClient.getAllTags(); + assertEquals(1, tags.size() - countBefore); + + saveAndRestoreClient.deleteTag(tagData); + + tags = saveAndRestoreClient.getAllTags(); + assertEquals(countBefore, tags.size()); + + saveAndRestoreClient.deleteNodes(List.of(configuration.getConfigurationNode().getUniqueId())); + } + + @Test + public void testCreateAndUpdateCompositeSnapshot() { + Node node = Node.builder() + .nodeType(NodeType.CONFIGURATION) + .name("config") + .description("description") + .build(); + + ConfigurationData configurationData = new ConfigurationData(); + ConfigPv configPv = ConfigPv.builder() + .pvName("pv") + .readbackPvName("readback") + .build(); + configurationData.setPvList(List.of(configPv)); + Configuration configuration = new Configuration(); + configuration.setConfigurationData(configurationData); + configuration.setConfigurationNode(node); + + configuration = saveAndRestoreClient.createConfiguration(topLevelTestNode.getUniqueId(), configuration); + + SnapshotItem snapshotItem = new SnapshotItem(); + snapshotItem.setConfigPv(configPv); + snapshotItem.setValue(VInt.of(777, Alarm.none(), Time.now(), Display.none())); + snapshotItem.setReadbackValue(VInt.of(42, Alarm.none(), Time.now(), Display.none())); + + SnapshotData snapshotData = new SnapshotData(); + snapshotData.setSnapshotItems(List.of(snapshotItem)); + + Node snapshotNode = Node.builder() + .nodeType(NodeType.SNAPSHOT) + .name("snapshot") + .description("description") + .build(); + Snapshot snapshot = new Snapshot(); + snapshot.setSnapshotData(snapshotData); + snapshot.setSnapshotNode(snapshotNode); + + snapshot = saveAndRestoreClient.createSnapshot(configuration.getConfigurationNode().getUniqueId(), + snapshot); + + Node compositeSnapshotNode = Node.builder() + .nodeType(NodeType.COMPOSITE_SNAPSHOT) + .name("composite") + .description("description") + .build(); + CompositeSnapshotData compositeSnapshotData = new CompositeSnapshotData(); + compositeSnapshotData.setReferencedSnapshotNodes(List.of(snapshot.getSnapshotNode().getUniqueId())); + CompositeSnapshot compositeSnapshot = new CompositeSnapshot(); + compositeSnapshot.setCompositeSnapshotData(compositeSnapshotData); + compositeSnapshot.setCompositeSnapshotNode(compositeSnapshotNode); + + compositeSnapshot = saveAndRestoreClient.createCompositeSnapshot(topLevelTestNode.getUniqueId(), compositeSnapshot); + + compositeSnapshot.getCompositeSnapshotNode().setName("another"); + compositeSnapshot = saveAndRestoreClient.updateCompositeSnapshot(compositeSnapshot); + + assertEquals("another", compositeSnapshot.getCompositeSnapshotNode().getName()); + + saveAndRestoreClient.deleteNodes(List.of(compositeSnapshot.getCompositeSnapshotNode().getUniqueId())); + } + + @Test + public void testCreateFilter() { + + List filters = saveAndRestoreClient.getAllFilters(); + int countBefore = filters.size(); + + Filter filter = new Filter(); + filter.setName("filter"); + filter.setQueryString("name=foo"); + + filter = saveAndRestoreClient.saveFilter(filter); + filters = saveAndRestoreClient.getAllFilters(); + + assertEquals(1, filters.size() - countBefore); + + saveAndRestoreClient.deleteFilter(filter.getName()); + + filters = saveAndRestoreClient.getAllFilters(); + + assertEquals(countBefore, filters.size()); + } + + @Test + public void testSearch() { + MultivaluedMap map = new MultivaluedHashMap<>(); + map.put("type", List.of("FOLDER")); + SearchResult searchResult = saveAndRestoreClient.search(map); + assertTrue(searchResult.getHitCount() > 0); + } + + @Test + public void testRestore() { + Node node = Node.builder() + .nodeType(NodeType.CONFIGURATION) + .name("config") + .description("description") + .build(); + + ConfigurationData configurationData = new ConfigurationData(); + ConfigPv configPv = ConfigPv.builder() + .pvName("pv") + .readbackPvName("readback") + .build(); + configurationData.setPvList(List.of(configPv)); + Configuration configuration = new Configuration(); + configuration.setConfigurationData(configurationData); + configuration.setConfigurationNode(node); + + configuration = saveAndRestoreClient.createConfiguration(topLevelTestNode.getUniqueId(), configuration); + + SnapshotItem snapshotItem = new SnapshotItem(); + snapshotItem.setConfigPv(configPv); + snapshotItem.setValue(VInt.of(777, Alarm.none(), Time.now(), Display.none())); + snapshotItem.setReadbackValue(VInt.of(42, Alarm.none(), Time.now(), Display.none())); + + SnapshotData snapshotData = new SnapshotData(); + snapshotData.setSnapshotItems(List.of(snapshotItem)); + + Node snapshotNode = Node.builder() + .nodeType(NodeType.SNAPSHOT) + .name("snapshot") + .description("description") + .build(); + Snapshot snapshot = new Snapshot(); + snapshot.setSnapshotData(snapshotData); + snapshot.setSnapshotNode(snapshotNode); + + snapshot = saveAndRestoreClient.createSnapshot(configuration.getConfigurationNode().getUniqueId(), + snapshot); + + List results = saveAndRestoreClient.restore(snapshot.getSnapshotNode().getUniqueId()); + assertFalse(results.get(0).getErrorMsg().isEmpty()); + + results = saveAndRestoreClient.restore(List.of(snapshotItem)); + assertFalse(results.get(0).getErrorMsg().isEmpty()); + + saveAndRestoreClient.deleteNodes(List.of(configuration.getConfigurationNode().getUniqueId())); + } + + @Test + public void testTakeSnapshot() { + Node node = Node.builder() + .nodeType(NodeType.CONFIGURATION) + .name("config") + .description("description") + .build(); + + ConfigurationData configurationData = new ConfigurationData(); + ConfigPv configPv = ConfigPv.builder() + .pvName("pv") + .readbackPvName("readback") + .build(); + configurationData.setPvList(List.of(configPv)); + Configuration configuration = new Configuration(); + configuration.setConfigurationData(configurationData); + configuration.setConfigurationNode(node); + + configuration = saveAndRestoreClient.createConfiguration(topLevelTestNode.getUniqueId(), configuration); + + List snapshotItems = saveAndRestoreClient.takeSnapshot(configuration.getConfigurationNode().getUniqueId()); + assertEquals(1, snapshotItems.size()); + assertNull(snapshotItems.get(0).getValue()); + + saveAndRestoreClient.deleteNodes(List.of(configuration.getConfigurationNode().getUniqueId())); + + } + + /* + @Test + public void tetsFoo() throws Exception{ + FileInputStream fileInputStream = new FileInputStream(new File("/Users/georgweiss/tmp/tags")); + ObjectMapper objectMapper = new ObjectMapper(); + List tags = objectMapper.readValue(fileInputStream, new TypeReference<>() { + }); + System.out.println(tags.size()); + int withComments = 0; + int nonGolden = 0; + for(Tag t : tags){ + if(!t.getName().equals("golden")){ + nonGolden++; + } + if(t.getComment() != null && !t.getComment().isEmpty()){ + withComments++; + System.out.println(t.getName() + " " + t.getComment()); + } + } + System.out.println(nonGolden); + System.out.println(withComments); + fileInputStream.close(); + } + + */ +} From b58f40497eb4732b26f0306a7dbb61c0a346a82d Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 26 Sep 2024 16:17:35 +0200 Subject: [PATCH 10/11] Finishing touches: HTTP DELETE with body possbile if wired 'manually' --- app/save-and-restore/app/pom.xml | 12 - .../client/SaveAndRestoreClientImpl.java | 53 +- .../client/SaveAndRestoreJerseyClient.java | 729 ------------------ .../ui/SaveAndRestoreService.java | 2 - 4 files changed, 31 insertions(+), 765 deletions(-) delete mode 100644 app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java diff --git a/app/save-and-restore/app/pom.xml b/app/save-and-restore/app/pom.xml index f0dfac98bd..c1eea2f82d 100644 --- a/app/save-and-restore/app/pom.xml +++ b/app/save-and-restore/app/pom.xml @@ -41,18 +41,6 @@ ${jgit.version} - - - com.sun.jersey - jersey-core - 1.19 - - - com.sun.jersey - jersey-client - 1.19 - - com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index c69f97aa84..e9b99b8e70 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -129,6 +129,7 @@ public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClient /** * {@inheritDoc} + * * @param parentsUniqueId Unique id of the parent {@link Node} for the new {@link Node} * @param node A {@link Node} object that should be created (=persisted). * @return {@inheritDoc} @@ -178,31 +179,30 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { /** * {@inheritDoc} + * * @param nodeIds List of unique {@link Node} ids. */ @Override public void deleteNodes(List nodeIds) { - // Native HttpClient does not support body in DELETE, so need to delete one by one... - nodeIds.forEach(id -> { - try { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(Preferences.jmasarServiceUrl + "/node/" + id)) - .DELETE() - .header("Content-Type", CONTENT_TYPE_JSON) - .header("Authorization", getBasicAuthenticationHeader()) - .build(); - HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() != 200) { - throw new SaveAndRestoreClientException("Failed to delete node " + id + ", " + response.body()); - } - } catch (Exception e) { - throw new RuntimeException(e); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Preferences.jmasarServiceUrl + "/node")) + .method("DELETE", HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(nodeIds))) + .header("Content-Type", CONTENT_TYPE_JSON) + .header("Authorization", getBasicAuthenticationHeader()) + .build(); + HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new SaveAndRestoreClientException("Failed to delete nodes: " + response.body()); } - }); + } catch (Exception e) { + throw new RuntimeException(e); + } } /** * {@inheritDoc} + * * @return {@inheritDoc} */ @Override @@ -213,6 +213,7 @@ public List getAllTags() { /** * {@inheritDoc} + * * @param sourceNodeIds List of unique {@link Node} ids. * @param targetNodeId The unique id of the parent {@link Node} to which the source {@link Node}s are moved. * @return @@ -281,8 +282,9 @@ public ConfigurationData getConfigurationData(String nodeId) { /** * {@inheritDoc} - * @param parentNodeId Non-null and non-empty unique id of an existing parent {@link Node}, - * which must be of type {@link org.phoebus.applications.saveandrestore.model.NodeType#FOLDER}. + * + * @param parentNodeId Non-null and non-empty unique id of an existing parent {@link Node}, + * which must be of type {@link org.phoebus.applications.saveandrestore.model.NodeType#FOLDER}. * @param configuration {@link ConfigurationData} object * @return */ @@ -307,6 +309,7 @@ public Configuration createConfiguration(String parentNodeId, Configuration conf /** * {@inheritDoc} + * * @param configuration * @return */ @@ -337,8 +340,9 @@ public SnapshotData getSnapshotData(String uniqueId) { /** * {@inheritDoc} + * * @param parentNodeId The unique id of the configuration {@link Node} associated with the {@link Snapshot} - * @param snapshot The {@link Snapshot} data object. + * @param snapshot The {@link Snapshot} data object. * @return {@inheritDoc} */ @Override @@ -381,7 +385,8 @@ public Snapshot updateSnapshot(Snapshot snapshot) { /** * {@inheritDoc} - * @param parentNodeId The parent {@link Node} for the new {@link CompositeSnapshot} + * + * @param parentNodeId The parent {@link Node} for the new {@link CompositeSnapshot} * @param compositeSnapshot The data object * @return A {@link CompositeSnapshot} as persisted by the service. */ @@ -490,6 +495,7 @@ public Filter saveFilter(Filter filter) { /** * {@inheritDoc} + * * @return {@inheritDoc} */ @Override @@ -549,10 +555,10 @@ public List addTag(TagData tagData) { public List deleteTag(TagData tagData) { try { HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(Preferences.jmasarServiceUrl + "/delete-tags")) + .uri(URI.create(Preferences.jmasarServiceUrl + "/tags")) .header("Content-Type", CONTENT_TYPE_JSON) .header("Authorization", getBasicAuthenticationHeader()) - .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(tagData))) + .method("DELETE", HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(tagData))) .build(); HttpResponse response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { @@ -593,6 +599,7 @@ public UserData authenticate(String userName, String password) { /** * {@inheritDoc} + * * @param snapshotItems A {@link List} of {@link SnapshotItem}s * @return {@inheritDoc} */ @@ -618,6 +625,7 @@ public List restore(List snapshotItems) { /** * {@inheritDoc} + * * @param snapshotNodeId Unique id of a snapshot * @return {@inheritDoc} */ @@ -643,6 +651,7 @@ public List restore(String snapshotNodeId) { /** * {@inheritDoc} + * * @param configurationNodeId The unique id of the {@link Configuration} for which to take the snapshot * @return {@inheritDoc} */ diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java deleted file mode 100644 index 863115494f..0000000000 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ /dev/null @@ -1,729 +0,0 @@ -/* - * Copyright (C) 2020 European Spallation Source ERIC. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -package org.phoebus.applications.saveandrestore.client; - -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.GenericType; -import com.sun.jersey.api.client.UniformInterfaceException; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.client.config.ClientConfig; -import com.sun.jersey.api.client.config.DefaultClientConfig; -import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; -import org.phoebus.applications.saveandrestore.Messages; -import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; -import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; -import org.phoebus.applications.saveandrestore.model.Configuration; -import org.phoebus.applications.saveandrestore.model.ConfigurationData; -import org.phoebus.applications.saveandrestore.model.Node; -import org.phoebus.applications.saveandrestore.model.RestoreResult; -import org.phoebus.applications.saveandrestore.model.Snapshot; -import org.phoebus.applications.saveandrestore.model.SnapshotData; -import org.phoebus.applications.saveandrestore.model.SnapshotItem; -import org.phoebus.applications.saveandrestore.model.Tag; -import org.phoebus.applications.saveandrestore.model.TagData; -import org.phoebus.applications.saveandrestore.model.UserData; -import org.phoebus.applications.saveandrestore.model.search.Filter; -import org.phoebus.applications.saveandrestore.model.search.SearchResult; -import org.phoebus.security.store.SecureStore; -import org.phoebus.security.tokens.AuthenticationScope; -import org.phoebus.security.tokens.ScopedAuthenticationToken; - -import javax.ws.rs.core.MultivaluedMap; -import java.io.IOException; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class SaveAndRestoreJerseyClient implements org.phoebus.applications.saveandrestore.client.SaveAndRestoreClient { - - private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; - private static final Logger logger = Logger.getLogger(SaveAndRestoreJerseyClient.class.getName()); - - private static final int DEFAULT_READ_TIMEOUT = 5000; // ms - private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms - - private static final ObjectMapper mapper = new ObjectMapper(); - - /** - * Should be accessed through {@link #getClient()} to ensure proper usage of cached credentials, if available. - */ - private static final Client client; - - private static HTTPBasicAuthFilter httpBasicAuthFilter; - - static { - int httpClientReadTimeout = Preferences.httpClientReadTimeout > 0 ? Preferences.httpClientReadTimeout : DEFAULT_READ_TIMEOUT; - logger.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms"); - - int httpClientConnectTimeout = Preferences.httpClientConnectTimeout > 0 ? Preferences.httpClientConnectTimeout : DEFAULT_CONNECT_TIMEOUT; - logger.log(Level.INFO, "Save&restore client using connect timeout " + httpClientConnectTimeout + " ms"); - - DefaultClientConfig defaultClientConfig = new DefaultClientConfig(); - defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_READ_TIMEOUT, httpClientReadTimeout); - defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, httpClientConnectTimeout); - - JacksonJsonProvider jacksonJsonProvider = new JacksonJsonProvider(mapper); - defaultClientConfig.getSingletons().add(jacksonJsonProvider); - - client = Client.create(defaultClientConfig); - } - - public SaveAndRestoreJerseyClient() { - mapper.registerModule(new JavaTimeModule()); - mapper.setSerializationInclusion(Include.NON_NULL); - } - - private Client getClient() { - try { - SecureStore store = new SecureStore(); - ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE); - if (scopedAuthenticationToken != null) { - String username = scopedAuthenticationToken.getUsername(); - String password = scopedAuthenticationToken.getPassword(); - httpBasicAuthFilter = new HTTPBasicAuthFilter(username, password); - client.addFilter(httpBasicAuthFilter); - } else if (httpBasicAuthFilter != null) { - client.removeFilter(httpBasicAuthFilter); - } - } catch (Exception e) { - logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); - } - return client; - } - - @Override - public String getServiceUrl() { - return Preferences.jmasarServiceUrl; - } - - @Override - public Node getRoot() { - return getCall("/node/" + Node.ROOT_FOLDER_UNIQUE_ID, Node.class); - } - - @Override - public Node getNode(String uniqueNodeId) { - return getCall("/node/" + uniqueNodeId, Node.class); - } - - @Override - public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes"); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - message = "N/A"; - } - throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message); - } - - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public List getCompositeSnapshotItems(String uniqueNodeId) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items"); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - message = "N/A"; - } - throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message); - } - - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public Node getParentNode(String uniqueNodeId) { - return getCall("/node/" + uniqueNodeId + "/parent", Node.class); - } - - @Override - public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClientException { - ClientResponse response = getCall("/node/" + uniqueNodeId + "/children"); - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public Node createNewNode(String parentNodeId, Node node) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node") - .queryParam("parentNodeId", parentNodeId); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(node, CONTENT_TYPE_JSON) - .put(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.createNodeFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(Node.class); - } - - @Override - public Node updateNode(Node nodeToUpdate) { - return updateNode(nodeToUpdate, false); - } - - @Override - public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node") - .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false"); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(nodeToUpdate, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.updateNodeFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - - return response.getEntity(Node.class); - } - - private T getCall(String relativeUrl, Class clazz) { - ClientResponse response = getCall(relativeUrl); - return response.getEntity(clazz); - } - - private ClientResponse getCall(String relativeUrl) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + relativeUrl); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - message = "N/A"; - } - throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message); - } - - return response; - } - - @Override - public void deleteNodes(List nodeIds) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/node"); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(nodeIds, CONTENT_TYPE_JSON) - .delete(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = response.getEntity(String.class); - throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message); - } - } - - @Override - public List getAllTags() { - ClientResponse response = getCall("/tags"); - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public Node moveNodes(List sourceNodeIds, String targetNodeId) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/move") - .queryParam("to", targetNodeId); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(sourceNodeIds, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.copyOrMoveNotAllowedBody; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(Node.class); - } - - @Override - public Node copyNodes(List sourceNodeIds, String targetNodeId) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/copy") - .queryParam("to", targetNodeId); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(sourceNodeIds, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.copyOrMoveNotAllowedBody; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(Node.class); - } - - @Override - public String getFullPath(String uniqueNodeId) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/path/" + uniqueNodeId); - ClientResponse response = webResource.get(ClientResponse.class); - - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - return null; - } - return response.getEntity(String.class); - } - - @Override - public ConfigurationData getConfigurationData(String nodeId) { - ClientResponse clientResponse = getCall("/config/" + nodeId); - return clientResponse.getEntity(ConfigurationData.class); - } - - @Override - public Configuration createConfiguration(String parentNodeId, Configuration configuration) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/config") - .queryParam("parentNodeId", parentNodeId); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(configuration, CONTENT_TYPE_JSON) - .put(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.createConfigurationFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(Configuration.class); - } - - @Override - public Configuration updateConfiguration(Configuration configuration) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/config"); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(configuration, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.updateConfigurationFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new RuntimeException(message); - } - return response.getEntity(Configuration.class); - } - - @Override - public SnapshotData getSnapshotData(String nodeId) { - ClientResponse clientResponse = getCall("/snapshot/" + nodeId); - return clientResponse.getEntity(SnapshotData.class); - } - - @Override - public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/snapshot") - .queryParam("parentNodeId", parentNodeId); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .entity(snapshot, CONTENT_TYPE_JSON) - .put(ClientResponse.class); - } catch (UniformInterfaceException e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.searchFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(Snapshot.class); - } - - @Override - public Snapshot updateSnapshot(Snapshot snapshot) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/snapshot"); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .entity(snapshot, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - } catch (UniformInterfaceException e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.searchFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(Snapshot.class); - } - - /** - * {@inheritDoc} - * @param parentNodeId The parent {@link Node} for the new {@link CompositeSnapshot} - * @param compositeSnapshot The data object - * @return - */ - @Override - public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot") - .queryParam("parentNodeId", parentNodeId); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(compositeSnapshot, CONTENT_TYPE_JSON) - .put(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.createConfigurationFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(CompositeSnapshot.class); - } - - /** - * @param snapshotNodeIds List of {@link Node} ids corresponding to {@link Node}s of types - * {@link org.phoebus.applications.saveandrestore.model.NodeType#SNAPSHOT} - * and {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT} - * @return - */ - @Override - public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot-consistency-check"); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(snapshotNodeIds, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.compositeSnapshotConsistencyCheckFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/composite-snapshot"); - - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(compositeSnapshot, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.updateConfigurationFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new RuntimeException(message); - } - return response.getEntity(CompositeSnapshot.class); - } - - @Override - public SearchResult search(MultivaluedMap searchParams) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/search") - .queryParams(searchParams); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .get(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.searchFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new RuntimeException(message); - } - return response.getEntity(SearchResult.class); - } - - @Override - public Filter saveFilter(Filter filter) { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filter"); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .entity(filter, CONTENT_TYPE_JSON) - .put(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.saveFilterFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new RuntimeException(message); - } - return response.getEntity(Filter.class); - } - - @Override - public List getAllFilters() { - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filters"); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .get(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.searchFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new RuntimeException(message); - } - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public void deleteFilter(String name) { - // Filter name may contain space chars, need to URL encode these. - String filterName = name.replace(" ", "%20"); - WebResource webResource = getClient().resource(Preferences.jmasarServiceUrl + "/filter/" + filterName); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) - .delete(ClientResponse.class); - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.deleteFilterFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new RuntimeException(message); - } - } - - /** - * Adds a tag to a list of unique node ids, see {@link TagData}. - * - * @param tagData see {@link TagData} - * @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids - * passed in the tagData parameter. - */ - public List addTag(TagData tagData) { - - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/tags"); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .entity(tagData, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - } catch (UniformInterfaceException e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.tagAddFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(new GenericType<>() { - }); - } - - /** - * Deletes a tag from a list of unique node ids, see {@link TagData} - * - * @param tagData see {@link TagData} - * @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids - * passed in the tagData parameter. - */ - public List deleteTag(TagData tagData) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/tags"); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .entity(tagData, CONTENT_TYPE_JSON) - .delete(ClientResponse.class); - } catch (UniformInterfaceException e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.tagAddFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public UserData authenticate(String userName, String password) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/login") - .queryParam("username", userName) - .queryParam("password", password); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .post(ClientResponse.class); - } catch (UniformInterfaceException e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = Messages.authenticationFailed; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public List restore(List snapshotItems) { - WebResource webResource = - getClient().resource(Preferences.jmasarServiceUrl + "/restore/items"); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .entity(snapshotItems, CONTENT_TYPE_JSON) - .post(ClientResponse.class); - } catch (UniformInterfaceException e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = "Restore failed"; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(new GenericType<>() { - }); - } - - public List restore(String snapshotNodeId) { - WebResource webResource = - getClient() - .resource(Preferences.jmasarServiceUrl + "/restore/node") - .queryParam("nodeId", snapshotNodeId); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .post(ClientResponse.class); - } catch (UniformInterfaceException e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = "Restore failed"; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(new GenericType<>() { - }); - } - - @Override - public List takeSnapshot(String configNodeId) { - WebResource webResource = - getClient() - .resource(Preferences.jmasarServiceUrl + "/take-snapshot/" + configNodeId); - ClientResponse response; - try { - response = webResource.accept(CONTENT_TYPE_JSON) - .get(ClientResponse.class); - } catch (Exception e) { - throw new RuntimeException(e); - } - if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { - String message = "Take snapshot failed"; - try { - message = new String(response.getEntityInputStream().readAllBytes()); - } catch (IOException e) { - logger.log(Level.WARNING, "Unable to parse response", e); - } - throw new SaveAndRestoreClientException(message); - } - return response.getEntity(new GenericType<>() { - }); - } -} diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java index d74d74afa0..61c5986035 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java @@ -34,7 +34,6 @@ import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.applications.saveandrestore.model.search.SearchResult; import org.phoebus.applications.saveandrestore.client.SaveAndRestoreClient; -import org.phoebus.applications.saveandrestore.client.SaveAndRestoreJerseyClient; import org.phoebus.core.vtypes.VDisconnectedData; import org.phoebus.pv.PV; @@ -71,7 +70,6 @@ public class SaveAndRestoreService { private final SaveAndRestoreClient saveAndRestoreClient; private SaveAndRestoreService() { - //saveAndRestoreClient = new SaveAndRestoreJerseyClient(); saveAndRestoreClient = new SaveAndRestoreClientImpl(); executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); } From 37b108d96253d01d0d5c18876175faa4d396a069 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 26 Sep 2024 16:40:46 +0200 Subject: [PATCH 11/11] Removed superfluous endpoint --- .../web/controllers/TagController.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java index a653943470..56c9b2057a 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/TagController.java @@ -85,20 +85,4 @@ public List addTag(@RequestBody TagData tagData, public List deleteTag(@RequestBody TagData tagData) { return nodeDAO.deleteTag(tagData); } - - /** - * Removes a {@link Tag} from specified list of target {@link Node}s. The {@link Tag} contained - * in tagData must be non-null, and its name must be non-null and non-empty. - *

- * This is exposed as an HTTP POST as the native {@link java.net.http.HttpClient} does not - * support a body in a DELETE request. - * - * @param tagData See {@link TagData} - * @return The list of updated {@link Node}s - */ - @PostMapping("/delete-tags") - @PreAuthorize("@authorizationHelper.mayAddOrDeleteTag(#tagData, #root)") - public List deleteTagsAsPost(@RequestBody TagData tagData) { - return nodeDAO.deleteTag(tagData); - } }