Skip to content

Commit bac9c63

Browse files
handle project under maintenance.
1 parent ec27b09 commit bac9c63

11 files changed

+282
-3
lines changed

src/main/java/edu/stanford/protege/gateway/OwlEntityService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import edu.stanford.protege.webprotege.ipc.*;
1414
import org.slf4j.*;
1515
import org.springframework.beans.factory.annotation.Value;
16+
import org.springframework.context.annotation.Lazy;
1617
import org.springframework.stereotype.Service;
1718

1819
import javax.annotation.Nonnull;
@@ -45,7 +46,7 @@ public class OwlEntityService {
4546
public OwlEntityService(EntityLinearizationService entityLinearizationService,
4647
EntityPostCoordinationService entityPostCoordinationService,
4748
EntityHistoryService entityHistoryService,
48-
@Nonnull EventDispatcher eventDispatcher,
49+
@Lazy @Nonnull EventDispatcher eventDispatcher,
4950
OntologyService ontologyService,
5051
ValidatorService validatorService) {
5152
this.entityLinearizationService = entityLinearizationService;

src/main/java/edu/stanford/protege/gateway/controllers/ProjectsController.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
import com.google.common.hash.Hashing;
55
import edu.stanford.protege.gateway.OwlEntityService;
6+
import edu.stanford.protege.gateway.SecurityContextHelper;
67
import edu.stanford.protege.gateway.dto.*;
8+
import edu.stanford.protege.gateway.maintenance.ProjectMaintenanceState;
9+
import edu.stanford.protege.gateway.maintenance.ProjectUnderMaintenanceException;
10+
import edu.stanford.protege.webprotege.common.ProjectId;
711
import edu.stanford.protege.webprotege.ipc.util.CorrelationMDCUtil;
812
import io.swagger.v3.oas.annotations.Operation;
913
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -27,9 +31,11 @@
2731
public class ProjectsController {
2832

2933
private final OwlEntityService owlEntityService;
34+
private final ProjectMaintenanceState projectMaintenanceState;
3035

31-
public ProjectsController(OwlEntityService owlEntityService) {
36+
public ProjectsController(OwlEntityService owlEntityService, ProjectMaintenanceState projectMaintenanceState) {
3237
this.owlEntityService = owlEntityService;
38+
this.projectMaintenanceState = projectMaintenanceState;
3339
}
3440

3541

@@ -48,7 +54,7 @@ public ResponseEntity<List<ProjectSummaryDto>> getProjects() {
4854
@Operation(summary = "Reading an entity", operationId = "2_getEntity")
4955
public ResponseEntity<OWLEntityDto> getEntity(@PathVariable @javax.annotation.Nonnull String projectId, @RequestParam String entityIRI) {
5056
CorrelationMDCUtil.setCorrelationId(UUID.randomUUID().toString());
51-
57+
checkMaintenanceState(ProjectId.valueOf(projectId));
5258
OWLEntityDto dto = owlEntityService.getEntityInfo(entityIRI, projectId);
5359
return getOwlEntityDtoResponseEntity(dto);
5460
}
@@ -59,6 +65,7 @@ public ResponseEntity<OWLEntityDto> updateEntity(@RequestHeader(value = "If-Matc
5965
@PathVariable @Nonnull String projectId,
6066
@RequestBody @Valid OWLEntityDto owlEntityDto) {
6167
CorrelationMDCUtil.setCorrelationId(UUID.randomUUID().toString());
68+
checkMaintenanceState(ProjectId.valueOf(projectId));
6269

6370
OWLEntityDto response = owlEntityService.updateEntity(owlEntityDto, projectId, ifMatch);
6471
return getOwlEntityDtoResponseEntity(response);
@@ -71,6 +78,7 @@ public ResponseEntity<OWLEntityDto> createEntity(@PathVariable("projectId")
7178
String projectId,
7279
@RequestBody CreateEntityDto createEntityDto) {
7380
CorrelationMDCUtil.setCorrelationId(UUID.randomUUID().toString());
81+
checkMaintenanceState(ProjectId.valueOf(projectId));
7482

7583
var newCreatedIri = owlEntityService.createClassEntity(projectId, createEntityDto);
7684
OWLEntityDto result = owlEntityService.getEntityInfo(newCreatedIri, projectId);
@@ -82,6 +90,7 @@ public ResponseEntity<OWLEntityDto> createEntity(@PathVariable("projectId")
8290
@Operation(summary = "Get children for an entity", operationId = "5_getEntityChildren")
8391
public ResponseEntity<EntityChildren> getEntityChildren(@PathVariable String projectId, @RequestParam String entityIRI) {
8492
CorrelationMDCUtil.setCorrelationId(UUID.randomUUID().toString());
93+
checkMaintenanceState(ProjectId.valueOf(projectId));
8594

8695
List<String> children = owlEntityService.getEntityChildren(entityIRI, projectId);
8796

@@ -94,6 +103,7 @@ public ResponseEntity<EntityChildren> getEntityChildren(@PathVariable String pro
94103
@Operation(summary = "Comments for an entity", operationId = "6_getEntityComments")
95104
public ResponseEntity<EntityComments> getEntityComments(@PathVariable String projectId, @RequestParam String entityIRI) {
96105
CorrelationMDCUtil.setCorrelationId(UUID.randomUUID().toString());
106+
checkMaintenanceState(ProjectId.valueOf(projectId));
97107

98108
EntityComments entityComments = owlEntityService.getEntityComments(entityIRI, projectId);
99109

@@ -113,4 +123,10 @@ private static ResponseEntity<OWLEntityDto> getOwlEntityDtoResponseEntity(OWLEnt
113123
.eTag(etag)
114124
.body(dto);
115125
}
126+
127+
private void checkMaintenanceState(ProjectId projectId) {
128+
if(this.projectMaintenanceState.isUnderMaintenance(projectId, SecurityContextHelper.getExecutionContext())) {
129+
throw new ProjectUnderMaintenanceException(projectId.id());
130+
}
131+
}
116132
}

src/main/java/edu/stanford/protege/gateway/controllers/ValidationExceptionHandler.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package edu.stanford.protege.gateway.controllers;
22

3+
import edu.stanford.protege.gateway.maintenance.ProjectUnderMaintenanceException;
34
import org.springframework.http.ResponseEntity;
45
import org.springframework.web.bind.MethodArgumentNotValidException;
56
import org.springframework.web.bind.annotation.*;
@@ -8,6 +9,8 @@
89
import java.util.Map;
910
import java.util.stream.Collectors;
1011

12+
import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE;
13+
1114
@RestControllerAdvice
1215
public class ValidationExceptionHandler {
1316

@@ -34,4 +37,10 @@ public ResponseEntity<Map<String, String>> handleConstraintViolationException(Co
3437
Map<String, String> error = Map.of("error", errorMessage);
3538
return ResponseEntity.badRequest().body(error);
3639
}
40+
41+
@ExceptionHandler(ProjectUnderMaintenanceException.class)
42+
public ResponseEntity<Map<String, String>> handleProjectUnderMaintenanceException(ProjectUnderMaintenanceException ex) {
43+
Map<String, String> error = Map.of("error", ex.getMessage());
44+
return ResponseEntity.status(SERVICE_UNAVAILABLE).body(error);
45+
}
3746
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package edu.stanford.protege.gateway.maintenance;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.annotation.JsonTypeName;
6+
import edu.stanford.protege.webprotege.common.ProjectId;
7+
import edu.stanford.protege.webprotege.common.ProjectRequest;
8+
9+
import javax.annotation.Nonnull;
10+
11+
import static com.google.common.base.Preconditions.checkNotNull;
12+
13+
@JsonTypeName("webprotege.projects.GetProjectDetails")
14+
public record GetProjectDetailsAction(ProjectId projectId) implements ProjectRequest<GetProjectDetailsResult> {
15+
16+
public static final String CHANNEL = "webprotege.projects.GetProjectDetails";
17+
18+
@JsonCreator
19+
public GetProjectDetailsAction(@JsonProperty("projectId") ProjectId projectId) {
20+
this.projectId = checkNotNull(projectId);
21+
}
22+
23+
@Nonnull
24+
@Override
25+
public ProjectId projectId() {
26+
return projectId;
27+
}
28+
29+
@Override
30+
public String getChannel() {
31+
return CHANNEL;
32+
}
33+
}
34+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package edu.stanford.protege.gateway.maintenance;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.annotation.JsonTypeName;
6+
import edu.stanford.protege.webprotege.common.Response;
7+
8+
import javax.annotation.Nonnull;
9+
10+
@JsonTypeName("webprotege.projects.GetProjectDetails")
11+
public record GetProjectDetailsResult(@JsonProperty("projectDetails") @Nonnull ProjectDetails projectDetails) implements Response {
12+
13+
@JsonCreator
14+
public static GetProjectDetailsResult get(@JsonProperty("projectDetails") @Nonnull ProjectDetails projectDetails) {
15+
return new GetProjectDetailsResult(projectDetails);
16+
}
17+
}
18+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package edu.stanford.protege.gateway.maintenance;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import edu.stanford.protege.webprotege.common.DictionaryLanguage;
5+
import edu.stanford.protege.webprotege.common.ProjectId;
6+
import edu.stanford.protege.webprotege.common.UserId;
7+
import edu.stanford.protege.webprotege.lang.DisplayNameSettings;
8+
import edu.stanford.protege.webprotege.projectsettings.EntityDeprecationSettings;
9+
10+
import javax.annotation.Nonnull;
11+
import java.time.Instant;
12+
13+
public record ProjectDetails(
14+
@JsonProperty("_id") @Nonnull ProjectId projectId,
15+
@JsonProperty("displayName") @Nonnull String displayName,
16+
@JsonProperty("description") @Nonnull String description,
17+
@JsonProperty("owner") @Nonnull UserId owner,
18+
@JsonProperty("inTrash") boolean inTrash,
19+
@JsonProperty("defaultLanguage") @Nonnull DictionaryLanguage dictionaryLanguage,
20+
@JsonProperty("defaultDisplayNameSettings") @Nonnull DisplayNameSettings displayNameSettings,
21+
@JsonProperty("createdAt") @Nonnull Instant createdAt,
22+
@JsonProperty("createdBy") @Nonnull UserId createdBy,
23+
@JsonProperty("modifiedAt") @Nonnull Instant lastModifiedAt,
24+
@JsonProperty("modifiedBy") @Nonnull UserId lastModifiedBy,
25+
@JsonProperty("entityDeprecationSettings") @Nonnull EntityDeprecationSettings entityDeprecationSettings,
26+
@JsonProperty("underMaintenance") boolean underMaintenance
27+
) {
28+
/**
29+
* Gets the timestamp of when the project was created as milliseconds since epoch.
30+
*/
31+
public long getCreatedAt() {
32+
return createdAt.toEpochMilli();
33+
}
34+
35+
/**
36+
* Gets the timestamp of when the project was last modified as milliseconds since epoch.
37+
*/
38+
public long getLastModifiedAt() {
39+
return lastModifiedAt.toEpochMilli();
40+
}
41+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package edu.stanford.protege.gateway.maintenance;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import edu.stanford.protege.webprotege.common.ProjectId;
5+
import edu.stanford.protege.webprotege.ipc.ExecutionContext;
6+
import edu.stanford.protege.webprotege.ipc.impl.CommandExecutorImpl;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
10+
import org.springframework.stereotype.Component;
11+
12+
import java.util.concurrent.ConcurrentHashMap;
13+
import java.util.concurrent.ConcurrentMap;
14+
import java.util.concurrent.ExecutionException;
15+
16+
@Component
17+
public class ProjectMaintenanceState {
18+
19+
private final static Logger LOGGER = LoggerFactory.getLogger(ProjectMaintenanceState.class);
20+
21+
private final ConcurrentMap<ProjectId, Boolean> maintenanceState = new ConcurrentHashMap<>();
22+
private final CommandExecutorImpl<GetProjectDetailsAction, GetProjectDetailsResult> getProjectDetailsExecutor;
23+
24+
public ProjectMaintenanceState(ObjectMapper objectMapper, AsyncRabbitTemplate asyncRabbitTemplate) {
25+
this.getProjectDetailsExecutor = new CommandExecutorImpl<>(GetProjectDetailsResult.class);
26+
this.getProjectDetailsExecutor.setAsyncRabbitTemplate(asyncRabbitTemplate);
27+
this.getProjectDetailsExecutor.setObjectMapper(objectMapper);
28+
}
29+
30+
public void setUnderMaintenance(ProjectId projectId, boolean underMaintenance) {
31+
maintenanceState.put(projectId, underMaintenance);
32+
}
33+
34+
public boolean isUnderMaintenance(ProjectId projectId) {
35+
return maintenanceState.getOrDefault(projectId, false);
36+
}
37+
38+
public boolean isUnderMaintenance(ProjectId projectId, ExecutionContext executionContext) {
39+
Boolean cachedValue = maintenanceState.get(projectId);
40+
if (cachedValue != null) {
41+
return cachedValue;
42+
}
43+
44+
try {
45+
GetProjectDetailsResult result = getProjectDetailsExecutor.execute(
46+
new GetProjectDetailsAction(projectId),
47+
executionContext
48+
).get();
49+
50+
ProjectDetails projectDetails = result.projectDetails();
51+
boolean underMaintenance = projectDetails.underMaintenance();
52+
53+
maintenanceState.put(projectId, underMaintenance);
54+
55+
LOGGER.info("Fetched maintenance state for project {} from backend: {}", projectId.id(), underMaintenance);
56+
return underMaintenance;
57+
58+
} catch (InterruptedException | ExecutionException e) {
59+
LOGGER.error("Error fetching project details for project {} from backend", projectId.id(), e);
60+
// În caz de eroare, returnăm false (nu este sub maintenance)
61+
return false;
62+
}
63+
}
64+
public void removeProject(ProjectId projectId) {
65+
maintenanceState.remove(projectId);
66+
}
67+
}
68+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package edu.stanford.protege.gateway.maintenance;
2+
3+
import edu.stanford.protege.webprotege.ipc.EventHandler;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
import org.springframework.stereotype.Component;
7+
8+
import javax.annotation.Nonnull;
9+
10+
@Component
11+
public class ProjectUnderMaintenanceEventHandler implements EventHandler<ProjectUnderMaintenanceUpdateEvent> {
12+
13+
private final static Logger LOGGER = LoggerFactory.getLogger(ProjectUnderMaintenanceEventHandler.class);
14+
15+
private final ProjectMaintenanceState maintenanceState;
16+
17+
public ProjectUnderMaintenanceEventHandler(ProjectMaintenanceState maintenanceState) {
18+
this.maintenanceState = maintenanceState;
19+
}
20+
21+
@Nonnull
22+
@Override
23+
public String getChannelName() {
24+
return ProjectUnderMaintenanceUpdateEvent.CHANNEL;
25+
}
26+
27+
@Nonnull
28+
@Override
29+
public String getHandlerName() {
30+
return this.getClass().getName();
31+
}
32+
33+
@Override
34+
public Class<ProjectUnderMaintenanceUpdateEvent> getEventClass() {
35+
return ProjectUnderMaintenanceUpdateEvent.class;
36+
}
37+
38+
@Override
39+
public void handleEvent(ProjectUnderMaintenanceUpdateEvent event) {
40+
try {
41+
maintenanceState.setUnderMaintenance(event.projectId(), event.underMaintenance());
42+
LOGGER.info("Project {} maintenance state updated to: {}", event.projectId().id(), event.underMaintenance());
43+
} catch (Exception e) {
44+
LOGGER.error("Error handling ProjectUnderMaintenanceUpdateEvent for project {}", event.projectId().id(), e);
45+
}
46+
}
47+
}
48+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package edu.stanford.protege.gateway.maintenance;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.web.server.ResponseStatusException;
5+
6+
/**
7+
* Exception thrown when a project is under maintenance and requests cannot be processed.
8+
*/
9+
public class ProjectUnderMaintenanceException extends ResponseStatusException {
10+
11+
public ProjectUnderMaintenanceException(String projectId) {
12+
super(HttpStatus.SERVICE_UNAVAILABLE, "Project " + projectId + " is currently under maintenance");
13+
}
14+
}
15+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package edu.stanford.protege.gateway.maintenance;
2+
3+
import com.fasterxml.jackson.annotation.JsonTypeName;
4+
import edu.stanford.protege.webprotege.common.EventId;
5+
import edu.stanford.protege.webprotege.common.ProjectEvent;
6+
import edu.stanford.protege.webprotege.common.ProjectId;
7+
8+
@JsonTypeName(ProjectUnderMaintenanceUpdateEvent.CHANNEL)
9+
public record ProjectUnderMaintenanceUpdateEvent(ProjectId projectId, EventId eventId, boolean underMaintenance) implements ProjectEvent {
10+
11+
public final static String CHANNEL = "webprotege.events.projects.ProjectUnderMaintenanceUpdateEvent";
12+
13+
@Override
14+
public ProjectId projectId() {
15+
return projectId;
16+
}
17+
18+
@Override
19+
public EventId eventId() {
20+
return eventId;
21+
}
22+
23+
@Override
24+
public String getChannel() {
25+
return CHANNEL;
26+
}
27+
}
28+

0 commit comments

Comments
 (0)