Skip to content

Commit 9d342fa

Browse files
added under maintenance check on frontend calls.
1 parent 96549be commit 9d342fa

12 files changed

+289
-11
lines changed

src/main/java/edu/stanford/protege/webprotege/gateway/GatewayController.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,15 @@ public RpcResponse execute(@RequestBody RpcRequest request,
7474
CorrelationMDCUtil.setCorrelationId(UUID.randomUUID().toString());
7575
var result = rpcRequestProcessor.processRequest(request, accessToken, new UserId(userId));
7676
return result.get(timeoutInMs, TimeUnit.MILLISECONDS);
77+
} catch (ProjectUnderMaintenanceException e) {
78+
logger.info("Project is under maintenance. UserId: {} Request: {}", userId, request);
79+
throw e;
7780
} catch (ExecutionException e) {
78-
if(e.getCause() instanceof CommandExecutionException ex) {
81+
if(e.getCause() instanceof ProjectUnderMaintenanceException ex) {
82+
logger.info("Project is under maintenance (from ExecutionException). UserId: {} Request: {}", userId, request);
83+
throw ex;
84+
}
85+
else if(e.getCause() instanceof CommandExecutionException ex) {
7986
logger.info("Error with cause that is a CommandExecutionException. Mapping to a ResponseStatusException: {}", ex.getStatus());
8087
throw new ResponseStatusException(ex.getStatus(), e.getMessage());
8188
}

src/main/java/edu/stanford/protege/webprotege/gateway/MessengerImpl.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package edu.stanford.protege.webprotege.gateway;
22

3+
import edu.stanford.protege.webprotege.common.ProjectId;
34
import edu.stanford.protege.webprotege.common.UserId;
5+
import edu.stanford.protege.webprotege.gateway.websocket.ProjectMaintenanceState;
6+
import edu.stanford.protege.webprotege.ipc.ExecutionContext;
47
import edu.stanford.protege.webprotege.ipc.Headers;
8+
import java.util.UUID;
59
import org.slf4j.Logger;
610
import org.slf4j.LoggerFactory;
7-
import org.springframework.amqp.AmqpException;
811
import org.springframework.amqp.core.MessageBuilder;
912
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
1013

1114
import java.util.HashMap;
12-
import java.util.Map;
13-
import java.util.UUID;
1415
import java.util.concurrent.CompletableFuture;
1516

1617
/**
@@ -23,10 +24,12 @@ public class MessengerImpl implements Messenger {
2324
private final static Logger LOGGER = LoggerFactory.getLogger(MessengerImpl.class);
2425

2526
private final AsyncRabbitTemplate asyncRabbitTemplate;
27+
private final ProjectMaintenanceState maintenanceState;
2628

2729

28-
public MessengerImpl(AsyncRabbitTemplate asyncRabbitTemplate) {
30+
public MessengerImpl(AsyncRabbitTemplate asyncRabbitTemplate, ProjectMaintenanceState maintenanceState) {
2931
this.asyncRabbitTemplate = asyncRabbitTemplate;
32+
this.maintenanceState = maintenanceState;
3033
}
3134

3235
/**
@@ -45,8 +48,14 @@ public CompletableFuture<Msg> sendAndReceive(RpcRequest rpcRequest, String acces
4548
headers.put(Headers.CORRELATION_ID, CorrelationMDCUtil.getCorrelationId());
4649
LOGGER.info("Setting correlationId : " + CorrelationMDCUtil.getCorrelationId());
4750
if (rpcRequest.params().has("projectId")) {
48-
var projectId = rpcRequest.params().get("projectId").asText();
49-
headers.put(Headers.PROJECT_ID, projectId);
51+
var projectIdString = rpcRequest.params().get("projectId").asText();
52+
headers.put(Headers.PROJECT_ID, projectIdString);
53+
54+
var projectId = ProjectId.valueOf(projectIdString);
55+
var executionContext = new ExecutionContext(userId, accessToken, UUID.randomUUID().toString());
56+
if (maintenanceState.isUnderMaintenance(projectId, executionContext)) {
57+
throw new ProjectUnderMaintenanceException(projectIdString);
58+
}
5059
}
5160
LOGGER.info("User {} is sending: {}",userId.value(), rpcRequest.methodName());
5261
return asyncRabbitTemplate.sendAndReceive("webprotege-exchange", method, rabbitRequest)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package edu.stanford.protege.webprotege.gateway;
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+

src/main/java/edu/stanford/protege/webprotege/gateway/RpcRequestProcessor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ private CompletableFuture<RpcResponse> sendMessage(RpcRequest request, String ac
7676
payload, userId);
7777
return reply
7878
.exceptionally(e -> {
79-
// Convert all exceptions to a ResponseStatusException
79+
if (e instanceof ProjectUnderMaintenanceException) {
80+
throw (ProjectUnderMaintenanceException) e;
81+
}
8082
logger.error("Error during send and receive: {}. Returning failed future with ResponseStatusException HTTP 500 Internal Server Error", e.getMessage(), e);
8183
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
8284
})
@@ -90,6 +92,8 @@ private CompletableFuture<RpcResponse> sendMessage(RpcRequest request, String ac
9092
return CompletableFuture.failedFuture(new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e));
9193
}
9294
});
95+
} catch (ProjectUnderMaintenanceException e) {
96+
return CompletableFuture.failedFuture(e);
9397
} catch (Exception e) {
9498
return CompletableFuture.failedFuture(new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e));
9599
}

src/main/java/edu/stanford/protege/webprotege/gateway/WebprotegeGwtApiGatewayApplication.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ RpcRequestProcessor rpcRequestProcessor(@Value("${spring.application.name}") Str
5353

5454
@Bean
5555
@Lazy
56-
MessengerImpl messageHandler(AsyncRabbitTemplate rabbitTemplate) {
57-
return new MessengerImpl(rabbitTemplate);
56+
MessengerImpl messageHandler(AsyncRabbitTemplate rabbitTemplate, edu.stanford.protege.webprotege.gateway.websocket.ProjectMaintenanceState maintenanceState) {
57+
return new MessengerImpl(rabbitTemplate, maintenanceState);
5858
}
5959
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package edu.stanford.protege.webprotege.gateway.websocket;
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 edu.stanford.protege.webprotege.projects.GetProjectDetailsAction;
8+
import edu.stanford.protege.webprotege.projects.GetProjectDetailsResult;
9+
import edu.stanford.protege.webprotege.projects.ProjectDetails;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
13+
import org.springframework.stereotype.Component;
14+
15+
import java.util.concurrent.ConcurrentHashMap;
16+
import java.util.concurrent.ConcurrentMap;
17+
import java.util.concurrent.ExecutionException;
18+
19+
@Component
20+
public class ProjectMaintenanceState {
21+
22+
private final static Logger LOGGER = LoggerFactory.getLogger(ProjectMaintenanceState.class);
23+
24+
private final ConcurrentMap<ProjectId, Boolean> maintenanceState = new ConcurrentHashMap<>();
25+
private final CommandExecutorImpl<GetProjectDetailsAction, GetProjectDetailsResult> getProjectDetailsExecutor;
26+
27+
public ProjectMaintenanceState(ObjectMapper objectMapper, AsyncRabbitTemplate asyncRabbitTemplate) {
28+
this.getProjectDetailsExecutor = new CommandExecutorImpl<>(GetProjectDetailsResult.class);
29+
this.getProjectDetailsExecutor.setAsyncRabbitTemplate(asyncRabbitTemplate);
30+
this.getProjectDetailsExecutor.setObjectMapper(objectMapper);
31+
}
32+
33+
public void setUnderMaintenance(ProjectId projectId, boolean underMaintenance) {
34+
maintenanceState.put(projectId, underMaintenance);
35+
}
36+
37+
public boolean isUnderMaintenance(ProjectId projectId, ExecutionContext executionContext) {
38+
Boolean cachedValue = maintenanceState.get(projectId);
39+
if (cachedValue != null) {
40+
return cachedValue;
41+
}
42+
43+
try {
44+
GetProjectDetailsResult result = getProjectDetailsExecutor.execute(
45+
new GetProjectDetailsAction(projectId),
46+
executionContext
47+
).get();
48+
49+
ProjectDetails projectDetails = result.projectDetails();
50+
boolean underMaintenance = projectDetails.underMaintenance();
51+
52+
maintenanceState.put(projectId, underMaintenance);
53+
54+
LOGGER.info("Fetched maintenance state for project {} from backend: {}", projectId.id(), underMaintenance);
55+
return underMaintenance;
56+
57+
} catch (InterruptedException | ExecutionException e) {
58+
LOGGER.error("Error fetching project details for project {} from backend", projectId.id(), e);
59+
// În caz de eroare, returnăm false (nu este sub maintenance)
60+
return false;
61+
}
62+
}
63+
public void removeProject(ProjectId projectId) {
64+
maintenanceState.remove(projectId);
65+
}
66+
}
67+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package edu.stanford.protege.webprotege.gateway.websocket;
2+
3+
import edu.stanford.protege.webprotege.gateway.websocket.config.events.ProjectUnderMaintenanceUpdateEvent;
4+
import edu.stanford.protege.webprotege.ipc.EventHandler;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.springframework.stereotype.Component;
8+
9+
import javax.annotation.Nonnull;
10+
11+
@Component
12+
public class ProjectUnderMaintenanceEventHandler implements EventHandler<ProjectUnderMaintenanceUpdateEvent> {
13+
14+
private final static Logger LOGGER = LoggerFactory.getLogger(ProjectUnderMaintenanceEventHandler.class);
15+
16+
private final ProjectMaintenanceState maintenanceState;
17+
18+
public ProjectUnderMaintenanceEventHandler(ProjectMaintenanceState maintenanceState) {
19+
this.maintenanceState = maintenanceState;
20+
}
21+
22+
@Nonnull
23+
@Override
24+
public String getChannelName() {
25+
return ProjectUnderMaintenanceUpdateEvent.CHANNEL;
26+
}
27+
28+
@Nonnull
29+
@Override
30+
public String getHandlerName() {
31+
return this.getClass().getName();
32+
}
33+
34+
@Override
35+
public Class<ProjectUnderMaintenanceUpdateEvent> getEventClass() {
36+
return ProjectUnderMaintenanceUpdateEvent.class;
37+
}
38+
39+
@Override
40+
public void handleEvent(ProjectUnderMaintenanceUpdateEvent event) {
41+
try {
42+
maintenanceState.setUnderMaintenance(event.projectId(), event.underMaintenance());
43+
LOGGER.info("Project {} maintenance state updated to: {}", event.projectId().id(), event.underMaintenance());
44+
} catch (Exception e) {
45+
LOGGER.error("Error handling ProjectUnderMaintenanceUpdateEvent for project {}", event.projectId().id(), e);
46+
}
47+
}
48+
}
49+

src/main/java/edu/stanford/protege/webprotege/gateway/websocket/config/ObjectMapperConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import edu.stanford.protege.webprotege.event.*;
1313
import edu.stanford.protege.webprotege.frame.*;
1414
import edu.stanford.protege.webprotege.gateway.websocket.config.events.ParentsChangedEvent;
15+
import edu.stanford.protege.webprotege.gateway.websocket.config.events.ProjectUnderMaintenanceUpdateEvent;
1516
import edu.stanford.protege.webprotege.gateway.websocket.config.events.UpdateUiHistoryEvent;
1617
import edu.stanford.protege.webprotege.hierarchy.EntityHierarchyChangedEvent;
1718
import edu.stanford.protege.webprotege.issues.CommentPostedEvent;
@@ -330,7 +331,8 @@ public ObjectMapper objectMapper() {
330331
EntityTagsChangedEvent.class,
331332
ProjectTagsChangedEvent.class,
332333
WatchAddedEvent.class,
333-
WatchRemovedEvent.class);
334+
WatchRemovedEvent.class,
335+
ProjectUnderMaintenanceUpdateEvent.class);
334336

335337
mapper.registerModule(module);
336338

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package edu.stanford.protege.webprotege.gateway.websocket.config.events;
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+
import org.jetbrains.annotations.NotNull;
8+
9+
@JsonTypeName(ProjectUnderMaintenanceUpdateEvent.CHANNEL)
10+
public record ProjectUnderMaintenanceUpdateEvent(ProjectId projectId, EventId eventId, boolean underMaintenance) implements ProjectEvent {
11+
12+
public final static String CHANNEL = "webprotege.events.projects.ProjectUnderMaintenanceUpdateEvent";
13+
14+
@NotNull
15+
@Override
16+
public ProjectId projectId() {
17+
return projectId;
18+
}
19+
20+
@NotNull
21+
@Override
22+
public EventId eventId() {
23+
return eventId;
24+
}
25+
26+
@Override
27+
public String getChannel() {
28+
return CHANNEL;
29+
}
30+
}
31+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package edu.stanford.protege.webprotege.projects;
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+

0 commit comments

Comments
 (0)