You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2026/04/10 01:22:33 UTC
(james-project) 02/03: JAMES-4197 Add a DeserializationResult interface for deserialization results in EventSerializer
This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 2a9ef7bbc56622aee38c5a2814e8a3669efe5aa5
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Apr 7 11:31:37 2026 +0700
JAMES-4197 Add a DeserializationResult interface for deserialization results in EventSerializer
---
.../apache/james/events/DeserializationResult.java | 84 ++++++++++++++++++++++
.../org/apache/james/events/EventSerializer.java | 9 ++-
.../james/events/EventSerializersAggregator.java | 15 ++--
.../apache/james/events/EventBusTestFixture.java | 22 +++---
.../james/events/CassandraEventDeadLettersDAO.java | 2 +-
.../org/apache/james/events/GroupRegistration.java | 4 +-
.../james/events/GroupRegistrationHandler.java | 5 +-
.../james/events/KeyRegistrationHandler.java | 11 +--
.../apache/james/events/RabbitMQEventBusTest.java | 2 +-
.../james/events/PostgresEventDeadLetters.java | 2 +-
.../james/event/json/MailboxEventSerializer.scala | 16 ++---
.../james/jmap/change/JmapEventSerializer.scala | 14 ++--
12 files changed, 135 insertions(+), 51 deletions(-)
diff --git a/event-bus/api/src/main/java/org/apache/james/events/DeserializationResult.java b/event-bus/api/src/main/java/org/apache/james/events/DeserializationResult.java
new file mode 100644
index 0000000000..4fc4b02b6a
--- /dev/null
+++ b/event-bus/api/src/main/java/org/apache/james/events/DeserializationResult.java
@@ -0,0 +1,84 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.events;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.google.common.collect.ImmutableList;
+
+public sealed interface DeserializationResult permits DeserializationResult.Success, DeserializationResult.SuccessList, DeserializationResult.Failure {
+ static DeserializationResult of(Optional<Event> maybeEvent, String throwingMessage) {
+ return maybeEvent.<DeserializationResult>map(Success::new)
+ .orElse(new Failure(throwingMessage));
+ }
+
+ static DeserializationResult ofList(Optional<List<Event>> maybeEvents, String throwingMessage) {
+ return maybeEvents.<DeserializationResult>map(SuccessList::new)
+ .orElse(new Failure(throwingMessage));
+ }
+
+ Event event();
+
+ List<Event> events();
+
+ default boolean isSuccess() {
+ return this instanceof Success || this instanceof SuccessList;
+ }
+
+ record Success(Event event) implements DeserializationResult {
+ @Override
+ public Event event() {
+ return event;
+ }
+
+ @Override
+ public List<Event> events() {
+ return ImmutableList.of(event);
+ }
+ }
+
+ record SuccessList(List<Event> events) implements DeserializationResult {
+ @Override
+ public Event event() {
+ if (events.size() != 1) {
+ throw new IllegalStateException("Expected exactly one event but got " + events.size());
+ }
+ return events.getFirst();
+ }
+
+ @Override
+ public List<Event> events() {
+ return events;
+ }
+ }
+
+ record Failure(String throwingMessage) implements DeserializationResult {
+ @Override
+ public Event event() {
+ throw new RuntimeException(throwingMessage);
+ }
+
+ @Override
+ public List<Event> events() {
+ throw new RuntimeException(throwingMessage);
+ }
+ }
+}
diff --git a/event-bus/api/src/main/java/org/apache/james/events/EventSerializer.java b/event-bus/api/src/main/java/org/apache/james/events/EventSerializer.java
index c3bf6b3869..59b5d54a6a 100644
--- a/event-bus/api/src/main/java/org/apache/james/events/EventSerializer.java
+++ b/event-bus/api/src/main/java/org/apache/james/events/EventSerializer.java
@@ -21,7 +21,6 @@ package org.apache.james.events;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
-import java.util.List;
import java.util.Optional;
public interface EventSerializer {
@@ -37,15 +36,15 @@ public interface EventSerializer {
return toJson(event).map(json -> json.getBytes(StandardCharsets.UTF_8));
}
- Optional<Event> asEvent(String serialized);
+ DeserializationResult asEvent(String serialized);
- Optional<List<Event>> asEvents(String serialized);
+ DeserializationResult asEvents(String serialized);
- default Optional<Event> fromBytes(byte[] serialized) {
+ default DeserializationResult fromBytes(byte[] serialized) {
return asEvent(new String(serialized, StandardCharsets.UTF_8));
}
- default Optional<List<Event>> asEventsFromBytes(byte[] serialized) {
+ default DeserializationResult asEventsFromBytes(byte[] serialized) {
return asEvents(new String(serialized, StandardCharsets.UTF_8));
}
}
diff --git a/event-bus/api/src/main/java/org/apache/james/events/EventSerializersAggregator.java b/event-bus/api/src/main/java/org/apache/james/events/EventSerializersAggregator.java
index 86b8baf3f0..f663567f16 100644
--- a/event-bus/api/src/main/java/org/apache/james/events/EventSerializersAggregator.java
+++ b/event-bus/api/src/main/java/org/apache/james/events/EventSerializersAggregator.java
@@ -20,7 +20,6 @@
package org.apache.james.events;
import java.util.Collection;
-import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -45,11 +44,12 @@ public class EventSerializersAggregator implements EventSerializer {
}
@Override
- public Optional<Event> asEvent(String serialized) {
+ public DeserializationResult asEvent(String serialized) {
return allEventSerializers.stream()
.map(eventSerializer -> eventSerializer.asEvent(serialized))
- .flatMap(Optional::stream)
- .findFirst();
+ .filter(DeserializationResult::isSuccess)
+ .findFirst()
+ .orElse(new DeserializationResult.Failure("Could not deserialize event: " + serialized));
}
@Override
@@ -61,10 +61,11 @@ public class EventSerializersAggregator implements EventSerializer {
}
@Override
- public Optional<List<Event>> asEvents(String serialized) {
+ public DeserializationResult asEvents(String serialized) {
return allEventSerializers.stream()
.map(eventSerializer -> eventSerializer.asEvents(serialized))
- .flatMap(Optional::stream)
- .findFirst();
+ .filter(DeserializationResult::isSuccess)
+ .findFirst()
+ .orElse(new DeserializationResult.Failure("Could not deserialize events: " + serialized));
}
}
diff --git a/event-bus/api/src/test/java/org/apache/james/events/EventBusTestFixture.java b/event-bus/api/src/test/java/org/apache/james/events/EventBusTestFixture.java
index b56985cfc9..af0d419193 100644
--- a/event-bus/api/src/test/java/org/apache/james/events/EventBusTestFixture.java
+++ b/event-bus/api/src/test/java/org/apache/james/events/EventBusTestFixture.java
@@ -186,18 +186,18 @@ public interface EventBusTestFixture {
}
@Override
- public Optional<Event> asEvent(String serialized) {
+ public DeserializationResult asEvent(String serialized) {
if (!serialized.contains("&") || serialized.contains(ARRAY_SEPARATOR)) {
- return Optional.empty();
+ return new DeserializationResult.Failure("Invalid format: " + serialized);
}
List<String> parts = Splitter.on("&").splitToList(serialized);
if (!parts.get(0).equals(TestEvent.class.getCanonicalName())) {
- return Optional.empty();
+ return new DeserializationResult.Failure("Unknown event type: " + parts.get(0));
}
Event.EventId eventId = Event.EventId.of(UUID.fromString(parts.get(1)));
Username username = Username.of(Joiner.on("&").join(parts.stream().skip(2).collect(ImmutableList.toImmutableList())));
- return Optional.of(new TestEvent(eventId, username));
+ return new DeserializationResult.Success(new TestEvent(eventId, username));
}
@Override
@@ -209,12 +209,14 @@ public interface EventBusTestFixture {
}
@Override
- public Optional<List<Event>> asEvents(String serialized) {
- return Optional.of(Splitter.on(ARRAY_SEPARATOR)
- .splitToStream(serialized)
- .map(this::asEvent)
- .map(Optional::get)
- .collect(ImmutableList.toImmutableList()));
+ public DeserializationResult asEvents(String serialized) {
+ return DeserializationResult.ofList(
+ Optional.of(Splitter.on(ARRAY_SEPARATOR)
+ .splitToStream(serialized)
+ .map(this::asEvent)
+ .map(DeserializationResult::event)
+ .collect(ImmutableList.toImmutableList())),
+ "Could not deserialize events");
}
}
diff --git a/event-bus/cassandra/src/main/java/org/apache/james/events/CassandraEventDeadLettersDAO.java b/event-bus/cassandra/src/main/java/org/apache/james/events/CassandraEventDeadLettersDAO.java
index d7a547c643..183a3a733e 100644
--- a/event-bus/cassandra/src/main/java/org/apache/james/events/CassandraEventDeadLettersDAO.java
+++ b/event-bus/cassandra/src/main/java/org/apache/james/events/CassandraEventDeadLettersDAO.java
@@ -143,6 +143,6 @@ public class CassandraEventDeadLettersDAO {
private Event deserializeEvent(String serializedEvent) {
return eventSerializer.asEvent(serializedEvent)
- .orElseThrow(() -> new RuntimeException("Could not deserialize event: " + serializedEvent));
+ .event();
}
}
diff --git a/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistration.java b/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistration.java
index a3be6d6eeb..cecd93c61d 100644
--- a/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistration.java
+++ b/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistration.java
@@ -27,7 +27,6 @@ import static org.apache.james.backends.rabbitmq.Constants.evaluateAutoDelete;
import static org.apache.james.backends.rabbitmq.Constants.evaluateDurable;
import static org.apache.james.backends.rabbitmq.Constants.evaluateExclusive;
-import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -178,8 +177,7 @@ class GroupRegistration implements Registration {
}
private Mono<Event> deserializeEvent(byte[] eventAsBytes) {
- return Mono.fromCallable(() -> eventSerializer.fromBytes(eventAsBytes)
- .orElseThrow(() -> new RuntimeException("Could not deserialize event: " + new String(eventAsBytes, StandardCharsets.UTF_8))))
+ return Mono.fromCallable(() -> eventSerializer.fromBytes(eventAsBytes).event())
.subscribeOn(Schedulers.parallel());
}
diff --git a/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistrationHandler.java b/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistrationHandler.java
index 69e1add06a..46251ae7ff 100644
--- a/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistrationHandler.java
+++ b/event-bus/distributed/src/main/java/org/apache/james/events/GroupRegistrationHandler.java
@@ -152,9 +152,8 @@ public class GroupRegistrationHandler {
}
private Mono<List<Event>> deserializeEvents(byte[] eventAsBytes) {
- return Mono.fromCallable(() ->
- eventSerializer.asEventsFromBytes(eventAsBytes)
- .orElseThrow(() -> new RuntimeException("Could not deserialize events")));
+ return Mono.fromCallable(() -> eventSerializer.asEventsFromBytes(eventAsBytes)
+ .events());
}
void stop() {
diff --git a/event-bus/distributed/src/main/java/org/apache/james/events/KeyRegistrationHandler.java b/event-bus/distributed/src/main/java/org/apache/james/events/KeyRegistrationHandler.java
index 3a9c371678..aef8e8a88c 100644
--- a/event-bus/distributed/src/main/java/org/apache/james/events/KeyRegistrationHandler.java
+++ b/event-bus/distributed/src/main/java/org/apache/james/events/KeyRegistrationHandler.java
@@ -26,7 +26,6 @@ import static org.apache.james.backends.rabbitmq.Constants.evaluateDurable;
import static org.apache.james.backends.rabbitmq.Constants.evaluateExclusive;
import static org.apache.james.events.RabbitMQEventBus.EVENT_BUS_ID;
-import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
@@ -222,9 +221,11 @@ class KeyRegistrationHandler {
return deserializeEvents(bodyAsBytes);
}
- return eventSerializer.fromBytes(bodyAsBytes)
- .map(List::of)
- .orElse(deserializeEvents(bodyAsBytes));
+ DeserializationResult result = eventSerializer.fromBytes(bodyAsBytes);
+ if (result.isSuccess()) {
+ return List.of(result.event());
+ }
+ return deserializeEvents(bodyAsBytes);
}
private StructuredLogger structuredLogger(List<Event> events, RegistrationKey key) {
@@ -243,6 +244,6 @@ class KeyRegistrationHandler {
private List<Event> deserializeEvents(byte[] bodyAsBytes) {
return eventSerializer.asEventsFromBytes(bodyAsBytes)
- .orElseThrow(() -> new RuntimeException("Could not deserialize events: " + new String(bodyAsBytes, StandardCharsets.UTF_8)));
+ .events();
}
}
diff --git a/event-bus/distributed/src/test/java/org/apache/james/events/RabbitMQEventBusTest.java b/event-bus/distributed/src/test/java/org/apache/james/events/RabbitMQEventBusTest.java
index 38eb279be2..b1e531cd8a 100644
--- a/event-bus/distributed/src/test/java/org/apache/james/events/RabbitMQEventBusTest.java
+++ b/event-bus/distributed/src/test/java/org/apache/james/events/RabbitMQEventBusTest.java
@@ -431,7 +431,7 @@ class RabbitMQEventBusTest implements GroupContract.SingleEventBusGroupContract,
.blockFirst()
.getBody();
- return eventSerializer.asEvent(new String(eventInBytes, StandardCharsets.UTF_8)).get();
+ return eventSerializer.asEvent(new String(eventInBytes, StandardCharsets.UTF_8)).event();
}
}
}
diff --git a/event-bus/postgres/src/main/java/org/apache/james/events/PostgresEventDeadLetters.java b/event-bus/postgres/src/main/java/org/apache/james/events/PostgresEventDeadLetters.java
index 4c4bb07357..871b1f34e9 100644
--- a/event-bus/postgres/src/main/java/org/apache/james/events/PostgresEventDeadLetters.java
+++ b/event-bus/postgres/src/main/java/org/apache/james/events/PostgresEventDeadLetters.java
@@ -92,7 +92,7 @@ public class PostgresEventDeadLetters implements EventDeadLetters {
private Event deserializeEvent(Record record) {
String serializedEvent = record.get(EVENT);
return eventSerializer.asEvent(serializedEvent)
- .orElseThrow(() -> new RuntimeException("Could not deserialize event: " + serializedEvent));
+ .event();
}
@Override
diff --git a/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala b/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala
index b0ddc58daa..6a21ed08a9 100644
--- a/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala
+++ b/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala
@@ -30,7 +30,7 @@ import org.apache.james.core.quota.{QuotaCountLimit, QuotaCountUsage, QuotaSizeL
import org.apache.james.event.json.DTOs.SystemFlag.SystemFlag
import org.apache.james.event.json.DTOs._
import org.apache.james.events.Event.EventId
-import org.apache.james.events.{EventSerializer, Event => JavaEvent}
+import org.apache.james.events.{DeserializationResult, EventSerializer, Event => JavaEvent}
import org.apache.james.mailbox.MailboxSession.SessionId
import org.apache.james.mailbox.events.MailboxEvents.Added.IS_APPENDED
import org.apache.james.mailbox.events.MailboxEvents.{Added => JavaAdded, Expunged => JavaExpunged, FlagsUpdated => JavaFlagsUpdated, MailboxACLUpdated => JavaMailboxACLUpdated, MailboxAdded => JavaMailboxAdded, MailboxDeletion => JavaMailboxDeletion, MailboxRenamed => JavaMailboxRenamed, MailboxSubscribedEvent => JavaMailboxSubscribedEvent, MailboxUnsubscribedEvent => JavaMailboxUnsubscribedEvent, MessageContentDeletionEvent => JavaMessageContentDeletionEvent, QuotaUsageUpdatedEvent => [...]
@@ -466,21 +466,19 @@ class MailboxEventSerializer @Inject()(mailboxIdFactory: MailboxId.Factory, mess
override def toJson(event: util.Collection[JavaEvent]): Optional[String] = Optional.of(jsonSerialize.toJson(event))
- override def asEvents(serialized: String): Optional[util.List[JavaEvent]] = {
+ override def asEvents(serialized: String): DeserializationResult = {
val result = jsonSerialize.fromJsonAsEvents(serialized)
if (result.isError) {
- Optional.empty()
- } else {
- Optional.of(result.get.asJava)
+ List(new DeserializationResult.Failure(s"Could not deserialize events: $serialized")).asJava
}
+ new DeserializationResult.SuccessList(result.get.asJava)
}
- override def asEvent(serialized: String): Optional[JavaEvent] = {
+ override def asEvent(serialized: String): DeserializationResult = {
val result = fromJson(serialized)
if (result.isError) {
- Optional.empty()
- } else {
- Optional.of(result.get)
+ new DeserializationResult.Failure(s"Could not deserialize event: $serialized")
}
+ new DeserializationResult.Success(result.get)
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala
index 08118e10f8..65eb6c33e9 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala
@@ -23,11 +23,10 @@ import java.util
import java.util.Optional
import com.fasterxml.jackson.annotation.JsonProperty
-import com.google.common.collect.ImmutableList
import jakarta.inject.Inject
import org.apache.james.core.Username
import org.apache.james.events.Event.EventId
-import org.apache.james.events.{Event, EventSerializer}
+import org.apache.james.events.{DeserializationResult, Event, EventSerializer}
import org.apache.james.jmap.api.change.TypeStateFactory
import org.apache.james.jmap.api.model.{State, TypeName}
import org.apache.james.jmap.core.UuidState
@@ -87,11 +86,15 @@ case class JmapEventSerializer @Inject()(stateChangeEventDTOFactory: StateChange
override def toJson(event: Event): Optional[String] = genericSerializer.maybeSerialize(event)
- override def asEvent(serialized: String): Optional[Event] = genericSerializer.maybeDeserialize(serialized)
+ override def asEvent(serialized: String): DeserializationResult = DeserializationResult.of(
+ genericSerializer.maybeDeserialize(serialized),
+ "Could not deserialize event " + serialized)
override def toJsonBytes(event: Event): Optional[Array[Byte]] = genericSerializer.maybeSerializeToBytes(event)
- override def fromBytes(serialized: Array[Byte]): Optional[Event] = genericSerializer.maybeDeserializeFromBytes(serialized)
+ override def fromBytes(serialized: Array[Byte]): DeserializationResult = DeserializationResult.of(
+ genericSerializer.maybeDeserializeFromBytes(serialized),
+ "Could not deserialize event " + serialized.map(_.toChar).mkString)
override def toJson(event: util.Collection[Event]): Optional[String] = {
if (event.size() != 1) {
@@ -100,6 +103,5 @@ case class JmapEventSerializer @Inject()(stateChangeEventDTOFactory: StateChange
toJson(event.iterator().next())
}
- override def asEvents(serialized: String): Optional[util.List[Event]] =
- asEvent(serialized).map(event => ImmutableList.of(event))
+ override def asEvents(serialized: String): DeserializationResult = asEvent(serialized)
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org