From 5e42f43f926d53685dc576aadc9ba7f6d9933c4c Mon Sep 17 00:00:00 2001 From: Kaveh Shahedi Date: Thu, 14 Nov 2024 16:12:23 -0500 Subject: [PATCH] server: Use standard IMarker structure for storing bookmarks The new changes aim to re-structure the algorithm of storing bookmarks. Previously, we stored each bookmark (of an experiment) under a specific directory inside the .webapp directory of Trace Compass. Right now, we are using Eclipse's markers system to store and fetch the benchmarks. [Changed] Bookmark storing/fetching system is changed to IMarker Signed-off-by: Kaveh Shahedi --- .../services/BookmarkManagerServiceTest.java | 399 +++++++++++------- .../core/tests/stubs/BookmarkModelStub.java | 8 +- .../jersey/rest/core/model/Bookmark.java | 1 - .../core/model/BookmarkQueryParameters.java | 4 +- .../jersey/rest/core/services/Bookmark.java | 1 - .../core/services/BookmarkManagerService.java | 361 ++++++++-------- 6 files changed, 439 insertions(+), 335 deletions(-) diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/BookmarkManagerServiceTest.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/BookmarkManagerServiceTest.java index 2b5c5a2f..fabc5bef 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/BookmarkManagerServiceTest.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/BookmarkManagerServiceTest.java @@ -15,7 +15,10 @@ import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.UUID; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; @@ -26,6 +29,7 @@ import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.BookmarkModelStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.ExperimentModelStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.utils.RestServerTest; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -33,8 +37,8 @@ * Test class for BookmarkManagerService * * @author Kaveh Shahedi - * @since 10.1 */ +@SuppressWarnings("null") public class BookmarkManagerServiceTest extends RestServerTest { private static final String BOOKMARK_NAME = "TEST"; @@ -43,40 +47,101 @@ public class BookmarkManagerServiceTest extends RestServerTest { private static final @NonNull BookmarkModelStub BOOKMARK = new BookmarkModelStub(BOOKMARK_NAME, START_TIME, END_TIME); private ExperimentModelStub experiment; + private static final String START = "start"; + private static final String END = "end"; + + private static final String BOOKMARK_END_TIME_MATCH = "End time should match"; + private static final String BOOKMARK_NAME_MATCH = "Bookmark name should match"; + private static final String BOOKMARK_START_TIME_MATCH = "Start time should match"; + private static final String NON_EXISTENT_BOOKMARK_STATUS_CODE = "Should return 404 for non-existent bookmark"; + private static final String NON_EXISTENT_EXPERIMENT_STATUS_CODE = "Should return 404 for non-existent experiment"; + private static final String NON_NULL_BOOKMARK = "Created bookmark should not be null"; + private static final String NON_NULL_RESPONSE_BODY = "Response body should not be null"; + private static final String NON_NULL_UUID = "UUID should not be null"; + private static final String NON_NUMERIC_TIMES_STATUS_CODE = "Should return 400 for non-numeric times"; + private static final String SUCCESSFUL_BOOKMARK_CREATION = "Bookmark creation should succeed"; + private static final String SUCCESSFUL_STATUS_CODE = "Response status should be 200"; + /** - * Setup method to run before each test. Creates a clean experiment and removes all - * existing bookmarks. + * Setup method to run before each test */ @Before public void setUp() { // Create the experiment first experiment = assertPostExperiment(CONTEXT_SWITCHES_UST_NOT_INITIALIZED_STUB.getName(), - CONTEXT_SWITCHES_UST_NOT_INITIALIZED_STUB); + CONTEXT_SWITCHES_UST_NOT_INITIALIZED_STUB); assertNotNull("Experiment should not be null", experiment); - assertNotNull("Experiment UUID should not be null", experiment.getUUID()); + assertNotNull(NON_NULL_UUID, experiment.getUUID()); + } - // Get all existing bookmarks and delete them + /** + * Tear down method to run after each test + */ + @After + public void tearDown() { + // Remove all bookmarks WebTarget application = getApplicationEndpoint(); WebTarget bookmarkTarget = application.path(EXPERIMENTS) .path(experiment.getUUID().toString()) .path(BOOKMARKS); - Response response = bookmarkTarget.request().get(); - assertEquals("GET request for bookmarks should return 200", 200, response.getStatus()); + try (Response response = bookmarkTarget.request().get()) { + assertEquals("GET request for bookmarks should return 200", 200, response.getStatus()); - if (response.getStatus() == 200) { BookmarkModelStub[] existingBookmarks = response.readEntity(BookmarkModelStub[].class); assertNotNull("Bookmark array should not be null", existingBookmarks); for (BookmarkModelStub bookmark : existingBookmarks) { - Response deleteResponse = bookmarkTarget.path(bookmark.getUUID().toString()) - .request() - .delete(); - assertEquals("DELETE request should return 200", 200, deleteResponse.getStatus()); + try (Response deleteResponse = bookmarkTarget.path(bookmark.getUUID().toString()) + .request() + .delete()) { + assertEquals("DELETE request should return 200", 200, deleteResponse.getStatus()); + } } } } + /** + * Test the bookmark endpoints with invalid experiment UUID (i.e., + * non-existent). + */ + @Test + public void testBookmarkEndpointsInvalidExperiment() { + WebTarget application = getApplicationEndpoint(); + WebTarget bookmarkTarget = application.path(EXPERIMENTS) + .path(UUID.randomUUID().toString()) + .path(BOOKMARKS); + + // Test getting all bookmarks + try (Response response = bookmarkTarget.request().get()) { + assertEquals(NON_EXISTENT_EXPERIMENT_STATUS_CODE, 404, response.getStatus()); + } + + // Test getting a specific bookmark + try (Response response = bookmarkTarget.path(BOOKMARK.getUUID().toString()).request().get()) { + assertEquals(NON_EXISTENT_EXPERIMENT_STATUS_CODE, 404, response.getStatus()); + } + + // Test creating a bookmark + Map parameters = new HashMap<>(); + parameters.put(NAME, BOOKMARK_NAME); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(NON_EXISTENT_EXPERIMENT_STATUS_CODE, 404, response.getStatus()); + } + + // Test updating a bookmark + try (Response response = bookmarkTarget.path(BOOKMARK.getUUID().toString()).request().put(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(NON_EXISTENT_EXPERIMENT_STATUS_CODE, 404, response.getStatus()); + } + + // Test deleting a bookmark + try (Response response = bookmarkTarget.path(BOOKMARK.getUUID().toString()).request().delete()) { + assertEquals(NON_EXISTENT_EXPERIMENT_STATUS_CODE, 404, response.getStatus()); + } + } + /** * Test the creation of a bookmark with invalid parameters. */ @@ -90,27 +155,35 @@ public void testCreateBookmarkInvalidParams() { // Test with null name Map parameters = new HashMap<>(); parameters.put(NAME, null); - parameters.put("start", START_TIME); - parameters.put("end", END_TIME); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals("Should return 400 for null name", 400, response.getStatus()); - Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Should return 400 for null name", 400, response.getStatus()); + } - // Test with non-numeric start and end times + // Test with non-numeric start parameters.put(NAME, BOOKMARK_NAME); - parameters.put("start", "not a number"); - parameters.put("end", "not a number"); + parameters.put(START, "not a number"); + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(NON_NUMERIC_TIMES_STATUS_CODE, 400, response.getStatus()); + } - response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Should return 400 for non-numeric times", 400, response.getStatus()); + // Test with non-numeric end + parameters.put(START, START_TIME); + parameters.put(END, "not a number"); + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(NON_NUMERIC_TIMES_STATUS_CODE, 400, response.getStatus()); + } // Test with end time before start time parameters.put(NAME, BOOKMARK_NAME); - parameters.put("start", END_TIME); - parameters.put("end", START_TIME); + parameters.put(START, END_TIME); + parameters.put(END, START_TIME); + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals("Should return 400 for invalid time range", 400, response.getStatus()); + } - response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Should return 400 for invalid time range", 400, response.getStatus()); } /** @@ -125,52 +198,81 @@ public void testCreateBookmark() { Map parameters = new HashMap<>(); parameters.put(NAME, BOOKMARK.getName()); - parameters.put("start", START_TIME); - parameters.put("end", END_TIME); - - Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Response status should be 200", 200, response.getStatus()); - - BookmarkModelStub expStub = response.readEntity(BookmarkModelStub.class); - assertNotNull("Response body should not be null", expStub); - assertEquals("Bookmark name should match", BOOKMARK.getName(), expStub.getName()); - assertEquals("Start time should match", BOOKMARK.getStart(), expStub.getStart()); - assertEquals("End time should match", BOOKMARK.getEnd(), expStub.getEnd()); - assertNotNull("UUID should not be null", expStub.getUUID()); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); + + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(SUCCESSFUL_STATUS_CODE, 200, response.getStatus()); + + BookmarkModelStub expStub = response.readEntity(BookmarkModelStub.class); + assertNotNull(NON_NULL_RESPONSE_BODY, expStub); + assertEquals(BOOKMARK_NAME_MATCH, BOOKMARK.getName(), expStub.getName()); + assertEquals(BOOKMARK_START_TIME_MATCH, BOOKMARK.getStart(), expStub.getStart()); + assertEquals(BOOKMARK_END_TIME_MATCH, BOOKMARK.getEnd(), expStub.getEnd()); + assertNotNull(NON_NULL_UUID, expStub.getUUID()); + } } /** - * Test the creation of a bookmark with a repetitive name. + * Test the creation of a bookmark with no end time (i.e., just start time). */ @Test - public void testCreateBookmarkRepetitiveName() { + public void testCreateBookmarkNoEndTime() { WebTarget application = getApplicationEndpoint(); WebTarget bookmarkTarget = application.path(EXPERIMENTS) .path(experiment.getUUID().toString()) .path(BOOKMARKS); - // Create first bookmark Map parameters = new HashMap<>(); parameters.put(NAME, BOOKMARK.getName()); - parameters.put("start", START_TIME); - parameters.put("end", END_TIME); - - Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - BookmarkModelStub firstBookmark = response.readEntity(BookmarkModelStub.class); - assertEquals("First bookmark creation should succeed", 200, response.getStatus()); - assertNotNull("First bookmark should not be null", firstBookmark); + parameters.put(START, START_TIME); + parameters.put(END, START_TIME); + + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(SUCCESSFUL_STATUS_CODE, 200, response.getStatus()); + + BookmarkModelStub expStub = response.readEntity(BookmarkModelStub.class); + assertNotNull(NON_NULL_RESPONSE_BODY, expStub); + assertEquals(BOOKMARK_NAME_MATCH, BOOKMARK.getName(), expStub.getName()); + assertEquals(BOOKMARK_START_TIME_MATCH, BOOKMARK.getStart(), expStub.getStart()); + assertEquals(BOOKMARK_END_TIME_MATCH, BOOKMARK.getStart(), expStub.getEnd()); + assertNotNull(NON_NULL_UUID, expStub.getUUID()); + } + } - // Try to create second bookmark with same name but different times - parameters.replace("start", START_TIME + 1); - parameters.replace("end", END_TIME + 1); + /** + * Test the creation of a bookmark with a repetitive data. + */ + @Test + public void testCreateIdenticalBookmarks() { + WebTarget application = getApplicationEndpoint(); + WebTarget bookmarkTarget = application.path(EXPERIMENTS) + .path(experiment.getUUID().toString()) + .path(BOOKMARKS); - response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Should return conflict for duplicate name", 409, response.getStatus()); + Set uuids = new HashSet<>(); + for (int i = 0; i < 3; i++) { + Map parameters = new HashMap<>(); + parameters.put(NAME, BOOKMARK.getName()); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); + + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(SUCCESSFUL_STATUS_CODE, 200, response.getStatus()); + + BookmarkModelStub expStub = response.readEntity(BookmarkModelStub.class); + assertNotNull(NON_NULL_RESPONSE_BODY, expStub); + assertEquals(BOOKMARK_NAME_MATCH, BOOKMARK.getName(), expStub.getName()); + assertEquals(BOOKMARK_START_TIME_MATCH, BOOKMARK.getStart(), expStub.getStart()); + assertEquals(BOOKMARK_END_TIME_MATCH, BOOKMARK.getEnd(), expStub.getEnd()); + assertNotNull(NON_NULL_UUID, expStub.getUUID()); + + // Check if the UUID is unique + assertFalse("UUID should be unique", uuids.contains(expStub.getUUID())); + uuids.add(expStub.getUUID()); + } + } - // Verify the original bookmark wasn't modified - Response getResponse = bookmarkTarget.path(firstBookmark.getUUID().toString()).request().get(); - BookmarkModelStub retrievedBookmark = getResponse.readEntity(BookmarkModelStub.class); - assertEquals("Original bookmark should remain unchanged", firstBookmark, retrievedBookmark); } /** @@ -184,38 +286,42 @@ public void testGetAllBookmarks() { .path(BOOKMARKS); // Initially there should be no bookmarks - Response response = bookmarkTarget.request().get(); - BookmarkModelStub[] initialBookmarks = response.readEntity(BookmarkModelStub[].class); - assertEquals("Should start with no bookmarks", 0, initialBookmarks.length); + try (Response response = bookmarkTarget.request().get()) { + BookmarkModelStub[] initialBookmarks = response.readEntity(BookmarkModelStub[].class); + assertEquals("Should start with no bookmarks", 0, initialBookmarks.length); + } // Create multiple bookmarks Map parameters = new HashMap<>(); - parameters.put("start", START_TIME); - parameters.put("end", END_TIME); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); // Create first bookmark parameters.put(NAME, "Bookmark1"); - response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("First bookmark creation should succeed", 200, response.getStatus()); + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals("First bookmark creation should succeed", 200, response.getStatus()); + } // Create second bookmark parameters.put(NAME, "Bookmark2"); - response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Second bookmark creation should succeed", 200, response.getStatus()); + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals("Second bookmark creation should succeed", 200, response.getStatus()); + } // Get all bookmarks - response = bookmarkTarget.request().get(); - BookmarkModelStub[] allBookmarks = response.readEntity(BookmarkModelStub[].class); - assertEquals("Should have 2 bookmarks", 2, allBookmarks.length); - - // Verify bookmark properties - for (BookmarkModelStub bookmark : allBookmarks) { - assertNotNull("Bookmark should not be null", bookmark); - assertNotNull("Bookmark UUID should not be null", bookmark.getUUID()); - assertEquals("Start time should match", START_TIME, bookmark.getStart()); - assertEquals("End time should match", END_TIME, bookmark.getEnd()); - assertTrue("Name should be either Bookmark1 or Bookmark2", - bookmark.getName().equals("Bookmark1") || bookmark.getName().equals("Bookmark2")); + try (Response response = bookmarkTarget.request().get()) { + BookmarkModelStub[] allBookmarks = response.readEntity(BookmarkModelStub[].class); + assertEquals("Should have 2 bookmarks", 2, allBookmarks.length); + + // Verify bookmark properties + for (BookmarkModelStub bookmark : allBookmarks) { + assertNotNull("Bookmark should not be null", bookmark); + assertNotNull("Bookmark UUID should not be null", bookmark.getUUID()); + assertEquals(BOOKMARK_START_TIME_MATCH, START_TIME, bookmark.getStart()); + assertEquals(BOOKMARK_END_TIME_MATCH, END_TIME, bookmark.getEnd()); + assertTrue("Name should be either Bookmark1 or Bookmark2", + bookmark.getName().equals("Bookmark1") || bookmark.getName().equals("Bookmark2")); + } } } @@ -232,23 +338,28 @@ public void testGetSpecificBookmark() { // Create a bookmark Map parameters = new HashMap<>(); parameters.put(NAME, BOOKMARK.getName()); - parameters.put("start", START_TIME); - parameters.put("end", END_TIME); - - Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Bookmark creation should succeed", 200, response.getStatus()); - BookmarkModelStub createdBookmark = response.readEntity(BookmarkModelStub.class); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); + + BookmarkModelStub createdBookmark = null; + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(SUCCESSFUL_BOOKMARK_CREATION, 200, response.getStatus()); + createdBookmark = response.readEntity(BookmarkModelStub.class); + assertNotNull(NON_NULL_BOOKMARK, createdBookmark); + } // Test getting non-existent bookmark - Response nonExistentResponse = bookmarkTarget.path("non-existent-uuid").request().get(); - assertEquals("Should return 404 for non-existent bookmark", 404, nonExistentResponse.getStatus()); + try (Response nonExistentResponse = bookmarkTarget.path(experiment.getUUID().toString()).request().get()) { + assertEquals(NON_EXISTENT_BOOKMARK_STATUS_CODE, 404, nonExistentResponse.getStatus()); + } // Test getting existing bookmark - response = bookmarkTarget.path(createdBookmark.getUUID().toString()).request().get(); - assertEquals("Should successfully get existing bookmark", 200, response.getStatus()); + try (Response response = bookmarkTarget.path(createdBookmark.getUUID().toString()).request().get()) { + assertEquals("Should successfully get existing bookmark", 200, response.getStatus()); - BookmarkModelStub retrievedBookmark = response.readEntity(BookmarkModelStub.class); - assertEquals("Retrieved bookmark should match created bookmark", createdBookmark, retrievedBookmark); + BookmarkModelStub retrievedBookmark = response.readEntity(BookmarkModelStub.class); + assertEquals("Retrieved bookmark should match created bookmark", createdBookmark, retrievedBookmark); + } } /** @@ -264,44 +375,37 @@ public void testUpdateBookmark() { // Create initial bookmark Map parameters = new HashMap<>(); parameters.put(NAME, BOOKMARK.getName()); - parameters.put("start", START_TIME); - parameters.put("end", END_TIME); - - Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - BookmarkModelStub originalBookmark = response.readEntity(BookmarkModelStub.class); - assertEquals("Initial bookmark creation should succeed", 200, response.getStatus()); - - // Test updating non-existent bookmark - WebTarget nonExistentTarget = bookmarkTarget.path("non-existent-uuid"); - Response nonExistentResponse = nonExistentTarget.request() - .put(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Should return 404 for non-existent bookmark", 404, nonExistentResponse.getStatus()); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); + + BookmarkModelStub originalBookmark = null; + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + originalBookmark = response.readEntity(BookmarkModelStub.class); + assertEquals(SUCCESSFUL_BOOKMARK_CREATION, 200, response.getStatus()); + assertNotNull(NON_NULL_BOOKMARK, originalBookmark); + } // Test updating with invalid parameters - parameters.put("start", END_TIME); - parameters.put("end", START_TIME); - Response invalidResponse = bookmarkTarget.path(originalBookmark.getUUID().toString()) - .request() - .put(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - assertEquals("Should return 400 for invalid parameters", 400, invalidResponse.getStatus()); + parameters.put(START, END_TIME); + parameters.put(END, START_TIME); + try (Response invalidResponse = bookmarkTarget.path(originalBookmark.getUUID().toString()).request().put(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals("Should return 400 for invalid parameters", 400, invalidResponse.getStatus()); + } // Test successful update parameters.put("name", "Updated Name"); - parameters.put("start", START_TIME + 5); - parameters.put("end", END_TIME + 5); - - response = bookmarkTarget.path(originalBookmark.getUUID().toString()) - .request() - .put(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - - assertEquals("Update should succeed", 200, response.getStatus()); - BookmarkModelStub updatedBookmark = response.readEntity(BookmarkModelStub.class); - - assertNotNull("Updated bookmark should not be null", updatedBookmark); - assertEquals("UUID should remain the same", originalBookmark.getUUID(), updatedBookmark.getUUID()); - assertEquals("Name should be updated", "Updated Name", updatedBookmark.getName()); - assertEquals("Start time should be updated", START_TIME + 5, updatedBookmark.getStart()); - assertEquals("End time should be updated", END_TIME + 5, updatedBookmark.getEnd()); + parameters.put(START, START_TIME + 5); + parameters.put(END, END_TIME + 5); + try (Response response = bookmarkTarget.path(originalBookmark.getUUID().toString()).request().put(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals("Update should succeed", 200, response.getStatus()); + + BookmarkModelStub updatedBookmark = response.readEntity(BookmarkModelStub.class); + assertNotNull(NON_NULL_BOOKMARK, updatedBookmark); + assertEquals("UUID should be the same", originalBookmark.getUUID(), updatedBookmark.getUUID()); + assertEquals("Name should be updated", "Updated Name", updatedBookmark.getName()); + assertEquals("Start time should be updated", START_TIME + 5, updatedBookmark.getStart()); + assertEquals("End time should be updated", END_TIME + 5, updatedBookmark.getEnd()); + } } /** @@ -315,38 +419,41 @@ public void testDeleteBookmark() { .path(BOOKMARKS); // Try deleting non-existent bookmark - Response nonExistentResponse = bookmarkTarget.path("non-existent-uuid") - .request() - .delete(); - assertEquals("Should return 404 for non-existent bookmark", 404, nonExistentResponse.getStatus()); + try (Response nonExistentResponse = bookmarkTarget.path(experiment.getUUID().toString()).request().delete()) { + assertEquals(NON_EXISTENT_BOOKMARK_STATUS_CODE, 404, nonExistentResponse.getStatus()); + } // Create a bookmark to delete Map parameters = new HashMap<>(); parameters.put(NAME, BOOKMARK.getName()); - parameters.put("start", START_TIME); - parameters.put("end", END_TIME); - - Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList()))); - BookmarkModelStub createdBookmark = response.readEntity(BookmarkModelStub.class); - assertEquals("Bookmark creation should succeed", 200, response.getStatus()); + parameters.put(START, START_TIME); + parameters.put(END, END_TIME); + + BookmarkModelStub createdBookmark = null; + try (Response response = bookmarkTarget.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + createdBookmark = response.readEntity(BookmarkModelStub.class); + assertEquals(SUCCESSFUL_BOOKMARK_CREATION, 200, response.getStatus()); + assertNotNull(NON_NULL_BOOKMARK, createdBookmark); + } // Delete the bookmark - response = bookmarkTarget.path(createdBookmark.getUUID().toString()) - .request() - .delete(); - assertEquals("Delete should succeed", 200, response.getStatus()); - BookmarkModelStub deletedBookmark = response.readEntity(BookmarkModelStub.class); - assertEquals("Deleted bookmark should match created bookmark", createdBookmark, deletedBookmark); + try (Response response = bookmarkTarget.path(createdBookmark.getUUID().toString()).request().delete()) { + assertEquals("Delete should succeed", 200, response.getStatus()); + BookmarkModelStub deletedBookmark = response.readEntity(BookmarkModelStub.class); + assertEquals("Deleted bookmark should match created bookmark", createdBookmark, deletedBookmark); + } // Verify the bookmark is actually deleted - Response getResponse = bookmarkTarget.path(createdBookmark.getUUID().toString()) - .request() - .get(); - assertEquals("Should return 404 for deleted bookmark", 404, getResponse.getStatus()); + try (Response getResponse = bookmarkTarget.path(createdBookmark.getUUID().toString()).request().get()) { + assertEquals("Should return 404 for deleted bookmark", 404, getResponse.getStatus()); + } // Verify it's not in the list of all bookmarks - Response getAllResponse = bookmarkTarget.request().get(); - BookmarkModelStub[] allBookmarks = getAllResponse.readEntity(BookmarkModelStub[].class); - assertEquals("Should have no bookmarks after deletion", 0, allBookmarks.length); + try (Response getAllResponse = bookmarkTarget.request().get()) { + BookmarkModelStub[] allBookmarks = getAllResponse.readEntity(BookmarkModelStub[].class); + for (BookmarkModelStub bookmark : allBookmarks) { + assertNotEquals("Deleted bookmark should not be in list of all bookmarks", createdBookmark.getUUID(), bookmark.getUUID()); + } + } } } \ No newline at end of file diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/BookmarkModelStub.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/BookmarkModelStub.java index e3f8e3c5..ad3c45c2 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/BookmarkModelStub.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/BookmarkModelStub.java @@ -12,7 +12,6 @@ package org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs; import java.io.Serializable; -import java.nio.charset.Charset; import java.util.Objects; import java.util.UUID; @@ -25,7 +24,6 @@ * BookmarkModel schema * * @author Kaveh Shahedi - * @since 10.1 */ @JsonIgnoreProperties(ignoreUnknown = true) public class BookmarkModelStub implements Serializable { @@ -71,11 +69,7 @@ public BookmarkModelStub( * end time */ public BookmarkModelStub(String name, long start, long end) { - this(getUUID(name), name, start, end); - } - - private static UUID getUUID(String name) { - return UUID.nameUUIDFromBytes(Objects.requireNonNull(name.getBytes(Charset.defaultCharset()))); + this(UUID.randomUUID(), name, start, end); } /** diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/Bookmark.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/Bookmark.java index 61e66cbd..b85b33ba 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/Bookmark.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/Bookmark.java @@ -23,7 +23,6 @@ * Contributes to the model used for TSP swagger-core annotations. * * @author Kaveh Shahedi - * @since 10.1 */ public interface Bookmark { diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/BookmarkQueryParameters.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/BookmarkQueryParameters.java index 05e2e83a..e948710c 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/BookmarkQueryParameters.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/model/BookmarkQueryParameters.java @@ -19,7 +19,6 @@ * Parameters for bookmark creation and update operations * * @author Kaveh Shahedi - * @since 10.1 */ public interface BookmarkQueryParameters { @@ -31,6 +30,9 @@ public interface BookmarkQueryParameters { BookmarkParameters getParameters(); + /** + * Bookmark parameters + */ interface BookmarkParameters { /** * @return The bookmark name diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/Bookmark.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/Bookmark.java index d787a3ac..83a451fb 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/Bookmark.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/Bookmark.java @@ -19,7 +19,6 @@ * Bookmark model for TSP * * @author Kaveh Shahedi - * @since 10.1 */ public class Bookmark implements Serializable { diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/BookmarkManagerService.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/BookmarkManagerService.java index 8d3dbc72..020dc2a9 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/BookmarkManagerService.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core/src/org/eclipse/tracecompass/incubator/internal/trace/server/jersey/rest/core/services/BookmarkManagerService.java @@ -17,15 +17,8 @@ import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.NO_SUCH_EXPERIMENT; import static org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.EndpointConstants.BKM; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -44,22 +37,18 @@ import javax.ws.rs.core.Response.Status; import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceVisitor; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.osgi.util.NLS; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.Activator; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.BookmarkQueryParameters; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.views.QueryParameters; -import org.eclipse.tracecompass.tmf.core.TmfCommonConstants; +import org.eclipse.tracecompass.tmf.core.resources.ITmfMarker; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment; -import com.google.common.collect.Lists; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; @@ -73,60 +62,18 @@ * Service to manage bookmarks for experiments * * @author Kaveh Shahedi - * @since 10.1 */ @Path("/experiments/{expUUID}/bookmarks") @Tag(name = BKM) public class BookmarkManagerService { - private static final Map> EXPERIMENT_BOOKMARKS = Collections.synchronizedMap(initBookmarkResources()); - private static final String BOOKMARKS_FOLDER = "Bookmarks"; //$NON-NLS-1$ - - private static Map> initBookmarkResources() { - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IProject project = root.getProject(TmfCommonConstants.DEFAULT_TRACE_PROJECT_NAME); - Map> experimentBookmarks = new HashMap<>(); - try { - project.refreshLocal(IResource.DEPTH_INFINITE, null); - IFolder bookmarksFolder = project.getFolder(BOOKMARKS_FOLDER); - // Check if the folder exists. If not, create it - if (!bookmarksFolder.exists()) { - bookmarksFolder.create(true, true, null); - } - bookmarksFolder.accept((IResourceVisitor) resource -> { - if (resource.equals(bookmarksFolder)) { - return true; - } - if (resource instanceof IFolder) { - UUID expUUID = UUID.fromString(Objects.requireNonNull(resource.getName())); - Map bookmarks = loadBookmarks((IFolder) resource); - if (!bookmarks.isEmpty()) { - experimentBookmarks.put(expUUID, bookmarks); - } - } - return false; - }, IResource.DEPTH_ONE, IResource.NONE); - } catch (CoreException e) { - Activator.getInstance().logError("Failed to load bookmarks", e); //$NON-NLS-1$ - } - return experimentBookmarks; - } + // Bookmark attribute constants + private static final String BOOKMARK_UUID = "uuid"; //$NON-NLS-1$ + private static final String BOOKMARK_NAME = "name"; //$NON-NLS-1$ + private static final String BOOKMARK_START = "start"; //$NON-NLS-1$ + private static final String BOOKMARK_END = "end"; //$NON-NLS-1$ - private static Map loadBookmarks(IFolder experimentFolder) throws CoreException { - Map bookmarks = new HashMap<>(); - experimentFolder.accept(resource -> { - if (resource instanceof IFile && resource.getName().endsWith(".bookmark")) { //$NON-NLS-1$ - try (ObjectInputStream ois = new ObjectInputStream(((IFile) resource).getContents(true))) { - Bookmark bookmark = (Bookmark) ois.readObject(); - bookmarks.put(bookmark.getUUID(), bookmark); - } catch (Exception e) { - Activator.getInstance().logError("Failed to load bookmark", e); //$NON-NLS-1$ - } - } - return true; - }); - return bookmarks; - } + private static final String BOOKMARK_DEFAULT_COLOR = "RGBA {255, 0, 0, 128}"; //$NON-NLS-1$ /** * Retrieve all bookmarks for a specific experiment @@ -138,7 +85,7 @@ private static Map loadBookmarks(IFolder experimentFolder) throw @GET @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "Get all bookmarks for an experiment", responses = { - @ApiResponse(responseCode = "200", description = "Returns the list of bookmarks", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Bookmark.class)))), + @ApiResponse(responseCode = "200", description = "Returns the list of bookmarks", content = @Content(array = @ArraySchema(schema = @Schema(implementation = org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.Bookmark.class)))), @ApiResponse(responseCode = "404", description = NO_SUCH_EXPERIMENT, content = @Content(schema = @Schema(implementation = String.class))) }) public Response getBookmarks(@Parameter(description = EXP_UUID) @PathParam("expUUID") UUID expUUID) { @@ -147,9 +94,18 @@ public Response getBookmarks(@Parameter(description = EXP_UUID) @PathParam("expU return Response.status(Status.NOT_FOUND).entity(NO_SUCH_EXPERIMENT).build(); } - synchronized (EXPERIMENT_BOOKMARKS) { - List bookmarks = Lists.transform(new ArrayList<>(EXPERIMENT_BOOKMARKS.getOrDefault(expUUID, Collections.emptyMap()).values()), bookmark -> bookmark); + IFile editorFile = TmfTraceManager.getInstance().getTraceEditorFile(experiment); + if (editorFile == null) { + return Response.ok(Collections.emptyList()).build(); + } + + try { + IMarker[] markers = findBookmarkMarkers(editorFile); + List bookmarks = markersToBookmarks(markers); return Response.ok(bookmarks).build(); + } catch (CoreException e) { + Activator.getInstance().logError("Failed to get bookmarks", e); //$NON-NLS-1$ + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } @@ -166,7 +122,7 @@ public Response getBookmarks(@Parameter(description = EXP_UUID) @PathParam("expU @Path("/{bookmarkUUID}") @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "Get a specific bookmark from an experiment", responses = { - @ApiResponse(responseCode = "200", description = "Returns the bookmark", content = @Content(schema = @Schema(implementation = Bookmark.class))), + @ApiResponse(responseCode = "200", description = "Returns the bookmark", content = @Content(schema = @Schema(implementation = org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.Bookmark.class))), @ApiResponse(responseCode = "404", description = "Experiment or bookmark not found", content = @Content(schema = @Schema(implementation = String.class))) }) public Response getBookmark( @@ -178,12 +134,28 @@ public Response getBookmark( return Response.status(Status.NOT_FOUND).entity(NO_SUCH_EXPERIMENT).build(); } - Map bookmarks = EXPERIMENT_BOOKMARKS.get(expUUID); - if (bookmarks == null || !bookmarks.containsKey(bookmarkUUID)) { + IFile editorFile = TmfTraceManager.getInstance().getTraceEditorFile(experiment); + if (editorFile == null) { return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); } - return Response.ok(bookmarks.get(bookmarkUUID)).build(); + try { + IMarker[] markers = findBookmarkMarkers(editorFile); + IMarker marker = findMarkerByUUID(markers, bookmarkUUID); + if (marker == null) { + return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); + } + + Bookmark bookmark = markerToBookmark(marker); + if (bookmark == null) { + return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); + } + + return Response.ok(bookmark).build(); + } catch (CoreException e) { + Activator.getInstance().logError("Failed to get bookmark", e); //$NON-NLS-1$ + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } } /** @@ -199,7 +171,7 @@ public Response getBookmark( @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "Create a new bookmark in an experiment", responses = { - @ApiResponse(responseCode = "200", description = "Bookmark created successfully", content = @Content(schema = @Schema(implementation = Bookmark.class))), + @ApiResponse(responseCode = "200", description = "Bookmark created successfully", content = @Content(schema = @Schema(implementation = org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.Bookmark.class))), @ApiResponse(responseCode = "400", description = INVALID_PARAMETERS, content = @Content(schema = @Schema(implementation = String.class))), @ApiResponse(responseCode = "404", description = NO_SUCH_EXPERIMENT, content = @Content(schema = @Schema(implementation = String.class))) }) @@ -224,53 +196,21 @@ public Response createBookmark( return Response.status(Status.NOT_FOUND).entity(NO_SUCH_EXPERIMENT).build(); } - String name = Objects.requireNonNull((String) parameters.get("name")); //$NON-NLS-1$ - long start = Objects.requireNonNull((Number) parameters.get("start")).longValue(); //$NON-NLS-1$ - long end = Objects.requireNonNull((Number) parameters.get("end")).longValue(); //$NON-NLS-1$ + IFile editorFile = TmfTraceManager.getInstance().getTraceEditorFile(experiment); + if (editorFile == null) { + return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); + } try { - IFolder bookmarkFolder = getBookmarkFolder(expUUID); - UUID bookmarkUUID = UUID.nameUUIDFromBytes(Objects.requireNonNull(name.getBytes(Charset.defaultCharset()))); - - // Check if bookmark already exists - Map existingBookmarks = EXPERIMENT_BOOKMARKS.get(expUUID); - if (existingBookmarks != null && existingBookmarks.containsKey(bookmarkUUID)) { - Bookmark existingBookmark = Objects.requireNonNull(existingBookmarks.get(bookmarkUUID)); - // Check if it's the same bookmark (same start and end times) - if (existingBookmark.getStart() != start || existingBookmark.getEnd() != end) { - // It's a different bookmark with the same name, return conflict - return Response.status(Status.CONFLICT).entity(existingBookmark).build(); - } - // It's the same bookmark, return it - return Response.ok(existingBookmark).build(); - } - - createFolder(bookmarkFolder); - - Bookmark bookmark = new Bookmark(bookmarkUUID, name, start, end); + String name = Objects.requireNonNull((String) parameters.get(BOOKMARK_NAME)); + long start = Objects.requireNonNull((Number) parameters.get(BOOKMARK_START)).longValue(); + long end = Objects.requireNonNull((Number) parameters.get(BOOKMARK_END)).longValue(); + UUID uuid = generateUUID(editorFile); - // Save to file system - IFile bookmarkFile = bookmarkFolder.getFile(bookmarkUUID.toString() + ".bookmark"); //$NON-NLS-1$ - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos)) { - - oos.writeObject(bookmark); - oos.flush(); - - if (bookmarkFile.exists()) { - bookmarkFile.setContents(new ByteArrayInputStream(baos.toByteArray()), IResource.FORCE, null); - } else { - bookmarkFile.create(new ByteArrayInputStream(baos.toByteArray()), true, null); - } - } catch (IOException e) { - Activator.getInstance().logError("Failed to create bookmark", e); //$NON-NLS-1$ - return Response.status(Status.INTERNAL_SERVER_ERROR).build(); - } - - // Add to memory - Map bookmarks = EXPERIMENT_BOOKMARKS.computeIfAbsent(expUUID, k -> new HashMap<>()); - bookmarks.put(bookmarkUUID, bookmark); + Bookmark bookmark = new Bookmark(uuid, name, start, end); + IMarker marker = editorFile.createMarker(IMarker.BOOKMARK); + setMarkerAttributes(marker, bookmark); return Response.ok(bookmark).build(); } catch (CoreException e) { Activator.getInstance().logError("Failed to create bookmark", e); //$NON-NLS-1$ @@ -294,7 +234,7 @@ public Response createBookmark( @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "Update an existing bookmark in an experiment", responses = { - @ApiResponse(responseCode = "200", description = "Bookmark updated successfully", content = @Content(schema = @Schema(implementation = Bookmark.class))), + @ApiResponse(responseCode = "200", description = "Bookmark updated successfully", content = @Content(schema = @Schema(implementation = org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.Bookmark.class))), @ApiResponse(responseCode = "400", description = INVALID_PARAMETERS, content = @Content(schema = @Schema(implementation = String.class))), @ApiResponse(responseCode = "404", description = "Experiment or bookmark not found", content = @Content(schema = @Schema(implementation = String.class))) }) @@ -320,42 +260,29 @@ public Response updateBookmark( return Response.status(Status.NOT_FOUND).entity(NO_SUCH_EXPERIMENT).build(); } - Map bookmarks = EXPERIMENT_BOOKMARKS.get(expUUID); - if (bookmarks == null || !bookmarks.containsKey(bookmarkUUID)) { + IFile editorFile = TmfTraceManager.getInstance().getTraceEditorFile(experiment); + if (editorFile == null) { return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); } - String name = Objects.requireNonNull((String) parameters.get("name")); //$NON-NLS-1$ - long start = Objects.requireNonNull((Number) parameters.get("start")).longValue(); //$NON-NLS-1$ - long end = Objects.requireNonNull((Number) parameters.get("end")).longValue(); //$NON-NLS-1$ + String name = Objects.requireNonNull((String) parameters.get(BOOKMARK_NAME)); + long start = Objects.requireNonNull((Number) parameters.get(BOOKMARK_START)).longValue(); + long end = Objects.requireNonNull((Number) parameters.get(BOOKMARK_END)).longValue(); + + Bookmark bookmark = new Bookmark(bookmarkUUID, name, start, end); try { - IFolder bookmarkFolder = getBookmarkFolder(expUUID); - Bookmark updatedBookmark = new Bookmark(bookmarkUUID, name, start, end); - - // Update file system - IFile bookmarkFile = bookmarkFolder.getFile(bookmarkUUID.toString() + ".bookmark"); //$NON-NLS-1$ - if (bookmarkFile.exists()) { - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos)) { - - oos.writeObject(updatedBookmark); - oos.flush(); - - bookmarkFile.setContents(new ByteArrayInputStream(baos.toByteArray()), IResource.FORCE, null); - // Update memory - bookmarks.put(bookmarkUUID, updatedBookmark); - return Response.ok(updatedBookmark).build(); - } + IMarker[] markers = findBookmarkMarkers(editorFile); + IMarker marker = findMarkerByUUID(markers, bookmarkUUID); + if (marker == null) { + return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); } - return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); + setMarkerAttributes(marker, bookmark); + return Response.ok(bookmark).build(); } catch (CoreException e) { Activator.getInstance().logError("Failed to update bookmark", e); //$NON-NLS-1$ return Response.status(Status.INTERNAL_SERVER_ERROR).build(); - } catch (IOException e) { - Activator.getInstance().logError("Failed to update bookmark", e); //$NON-NLS-1$ - return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } @@ -372,7 +299,7 @@ public Response updateBookmark( @Path("/{bookmarkUUID}") @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "Delete a bookmark from an experiment", responses = { - @ApiResponse(responseCode = "200", description = "Bookmark deleted successfully", content = @Content(schema = @Schema(implementation = Bookmark.class))), + @ApiResponse(responseCode = "200", description = "Bookmark deleted successfully", content = @Content(schema = @Schema(implementation = org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.Bookmark.class))), @ApiResponse(responseCode = "404", description = "Experiment or bookmark not found", content = @Content(schema = @Schema(implementation = String.class))) }) public Response deleteBookmark( @@ -384,28 +311,25 @@ public Response deleteBookmark( return Response.status(Status.NOT_FOUND).entity(NO_SUCH_EXPERIMENT).build(); } - Map bookmarks = EXPERIMENT_BOOKMARKS.get(expUUID); - if (bookmarks == null || !bookmarks.containsKey(bookmarkUUID)) { + IFile editorFile = TmfTraceManager.getInstance().getTraceEditorFile(experiment); + if (editorFile == null) { return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); } try { - IFolder bookmarkFolder = getBookmarkFolder(expUUID); - IFile bookmarkFile = bookmarkFolder.getFile(bookmarkUUID.toString() + ".bookmark"); //$NON-NLS-1$ - Bookmark deletedBookmark = bookmarks.remove(bookmarkUUID); - - if (bookmarkFile.exists()) { - bookmarkFile.delete(true, null); + IMarker[] markers = findBookmarkMarkers(editorFile); + IMarker marker = findMarkerByUUID(markers, bookmarkUUID); + if (marker == null) { + return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); } - if (bookmarks.isEmpty()) { - EXPERIMENT_BOOKMARKS.remove(expUUID); - if (bookmarkFolder.exists()) { - bookmarkFolder.delete(true, null); - } + Bookmark bookmark = markerToBookmark(marker); + if (bookmark == null) { + return Response.status(Status.NOT_FOUND).entity(EndpointConstants.BOOKMARK_NOT_FOUND).build(); } - return Response.ok(deletedBookmark).build(); + marker.delete(); + return Response.ok(bookmark).build(); } catch (CoreException e) { Activator.getInstance().logError("Failed to delete bookmark", e); //$NON-NLS-1$ return Response.status(Status.INTERNAL_SERVER_ERROR).build(); @@ -413,36 +337,115 @@ public Response deleteBookmark( } /** - * Gets the Eclipse resource folder for the bookmark. - * - * @param expUUID - * UUID of the experiment - * @return The Eclipse resource folder - * - * @throws CoreException - * if an error occurs + * Generate a random UUID for a new bookmark */ - private static @NonNull IFolder getBookmarkFolder(UUID expUUID) throws CoreException { - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IProject project = root.getProject(TmfCommonConstants.DEFAULT_TRACE_PROJECT_NAME); - project.refreshLocal(IResource.DEPTH_INFINITE, null); - IFolder bookmarksFolder = project.getFolder(BOOKMARKS_FOLDER); - return Objects.requireNonNull(bookmarksFolder.getFolder(expUUID.toString())); + private static UUID generateUUID(IFile editorFile) throws CoreException { + IMarker[] markers = findBookmarkMarkers(editorFile); + while (true) { + UUID uuid = UUID.randomUUID(); + + // Check if the UUID hasn't been used yet + if (findMarkerByUUID(markers, uuid) == null) { + return uuid; + } + } + } + + /** + * Find all bookmark markers in the editor file + */ + private static IMarker[] findBookmarkMarkers(IFile editorFile) throws CoreException { + return editorFile.findMarkers(IMarker.BOOKMARK, false, IResource.DEPTH_ZERO); + } + + /** + * Convert a marker to a Bookmark object + */ + private static Bookmark markerToBookmark(IMarker marker) { + String uuid = marker.getAttribute(BOOKMARK_UUID, (String) null); + if (uuid == null) { + return null; + } + + String name = marker.getAttribute(IMarker.MESSAGE, (String) null); + if (name == null) { + return null; + } + + String startStr = marker.getAttribute(ITmfMarker.MARKER_TIME, (String) null); + if (startStr == null) { + return null; + } + long start; + try { + start = Long.parseLong(startStr); + } catch (NumberFormatException e) { + return null; + } + + long duration = 0; + String durationStr = marker.getAttribute(ITmfMarker.MARKER_DURATION, (String) null); + if (durationStr != null) { + try { + duration = Long.parseLong(durationStr); + } catch (NumberFormatException e) { + return null; + } + } + long end = start + duration; + + return new Bookmark(UUID.fromString(uuid), name, start, end); } - private static void createFolder(IFolder folder) throws CoreException { - if (!folder.exists()) { - if (folder.getParent() instanceof IFolder) { - createFolder((IFolder) folder.getParent()); + /** + * Find a specific bookmark marker by UUID + */ + private static IMarker findMarkerByUUID(IMarker[] markers, UUID bookmarkUUID) { + for (IMarker marker : markers) { + String uuid = marker.getAttribute(BOOKMARK_UUID, (String) null); + if (uuid != null && UUID.fromString(uuid).equals(bookmarkUUID)) { + return marker; } - folder.create(true, true, null); } + return null; } /** - * Dispose method to be only called at server shutdown. + * Create a new marker with bookmark attributes */ - public static void dispose() { - EXPERIMENT_BOOKMARKS.clear(); + private static void setMarkerAttributes(IMarker marker, Bookmark bookmark) throws CoreException { + Long duration = bookmark.getEnd() - bookmark.getStart(); + + marker.setAttribute(BOOKMARK_UUID, bookmark.getUUID().toString()); + marker.setAttribute(IMarker.MESSAGE, bookmark.getName()); + marker.setAttribute(ITmfMarker.MARKER_TIME, Long.toString(bookmark.getStart())); + + if (duration > 0) { + marker.setAttribute(ITmfMarker.MARKER_DURATION, Long.toString(duration)); + marker.setAttribute(IMarker.LOCATION, + NLS.bind("timestamp [{0}, {1}]", //$NON-NLS-1$ + TmfTimestamp.fromNanos(bookmark.getStart()), + TmfTimestamp.fromNanos(bookmark.getEnd()))); + } else { + marker.setAttribute(IMarker.LOCATION, + NLS.bind("timestamp [{0}]", //$NON-NLS-1$ + TmfTimestamp.fromNanos(bookmark.getStart()))); + } + + marker.setAttribute(ITmfMarker.MARKER_COLOR, BOOKMARK_DEFAULT_COLOR); + } + + /** + * Convert list of markers to list of bookmarks + */ + private static List markersToBookmarks(IMarker[] markers) { + List bookmarks = new ArrayList<>(); + for (IMarker marker : markers) { + Bookmark bookmark = markerToBookmark(marker); + if (bookmark != null) { + bookmarks.add(bookmark); + } + } + return bookmarks; } } \ No newline at end of file