-
Notifications
You must be signed in to change notification settings - Fork 214
/
AbstractMongoSnapshotAdapter.java
188 lines (164 loc) · 7.15 KB
/
AbstractMongoSnapshotAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
* Copyright (c) 2017 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.internal.utils.persistence.mongo;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
import java.text.MessageFormat;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import org.bson.BsonValue;
import org.eclipse.ditto.base.model.exceptions.DittoJsonException;
import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
import org.eclipse.ditto.base.model.json.FieldType;
import org.eclipse.ditto.base.model.json.Jsonifiable;
import org.eclipse.ditto.internal.utils.persistence.SnapshotAdapter;
import org.eclipse.ditto.json.JsonField;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonParseException;
import org.slf4j.Logger;
import org.apache.pekko.persistence.SelectedSnapshot;
import org.apache.pekko.persistence.SnapshotOffer;
/**
* Abstract implementation of a MongoDB specific {@link SnapshotAdapter} for a {@link Jsonifiable}.
*
* @param <T> the jsonifiable type to snapshot.
*/
@ThreadSafe
public abstract class AbstractMongoSnapshotAdapter<T extends Jsonifiable.WithFieldSelectorAndPredicate<JsonField>>
implements SnapshotAdapter<T> {
private final Logger logger;
protected AbstractMongoSnapshotAdapter(final Logger logger) {
this.logger = logger;
}
/**
* Whether an entity is deleted.
*
* @param snapshotEntity the entity.
* @return whether it has the deleted lifecycle.
*/
protected abstract boolean isDeleted(final T snapshotEntity);
/**
* Return the snapshot JSON field for lifecycle of deleted entities.
*
* @return the empty snapshot JSON.
*/
protected abstract JsonField getDeletedLifecycleJsonField();
/**
* Return the revision JSON field if present in the entity.
*
* @return the revision JSON field.
*/
protected abstract Optional<JsonField> getRevisionJsonField(final T entity);
@Override
public Object toSnapshotStore(final T snapshotEntity) {
final JsonObject json = convertToJson(checkNotNull(snapshotEntity, "snapshot entity"));
onSnapshotStoreConversion(snapshotEntity, json);
final DittoBsonJson dittoBsonJson = DittoBsonJson.getInstance();
return dittoBsonJson.parse(json);
}
/**
* This method is called exactly once when a snapshot is created.
* It does nothing by default.
* Subclasses may override it to inject code.
*
* @param snapshotEntity The entity for which the snapshot is created.
* @param json The JSON object to store as snapshot.
*/
protected void onSnapshotStoreConversion(final T snapshotEntity, final JsonObject json) {
// does nothing by default
}
@Override
public T fromSnapshotStore(final SnapshotOffer snapshotOffer) {
return convertSnapshotToJsonifiable(snapshotOffer.snapshot());
}
@Override
public T fromSnapshotStore(final SelectedSnapshot selectedSnapshot) {
return convertSnapshotToJsonifiable(selectedSnapshot.snapshot());
}
/**
* Converts the specified snapshot entity to its {@link JsonObject} representation.
*
* @param snapshotEntity the snapshot entity to be converted to a JsonObject.
* @return {@code snapshotEntity} as JsonObject.
* @throws NullPointerException if {@code snapshotEntity} is {@code null}.
*/
protected JsonObject convertToJson(final T snapshotEntity) {
checkNotNull(snapshotEntity, "snapshot entity");
if (isDeleted(snapshotEntity)) {
final var builder = JsonObject.newBuilder().set(getDeletedLifecycleJsonField());
getRevisionJsonField(snapshotEntity).ifPresent(builder::set);
return builder.build();
} else {
return snapshotEntity.toJson(snapshotEntity.getImplementedSchemaVersion(), FieldType.all());
}
}
/**
* Algorithm to convert a raw snapshot entity to a Jsonifiable.
*
* @param rawSnapshotEntity the snapshot entity to be converted.
* @return a Jsonifiable whose origin is {@code rawSnapshotEntity} or {@code null}.
* @throws NullPointerException if {@code rawSnapshotEntity} is {@code null}.
*/
@Nullable
private T convertSnapshotToJsonifiable(final Object rawSnapshotEntity) {
return tryToCreateJsonifiableFrom(convertSnapshotEntityToJson(rawSnapshotEntity));
}
private static JsonObject convertSnapshotEntityToJson(final Object rawSnapshotEntity) {
checkNotNull(rawSnapshotEntity, "raw snapshot entity");
if (rawSnapshotEntity instanceof BsonValue bsonValue) {
return convertToJson(bsonValue);
}
final String pattern = "Unable to create a Jsonifiable from <{0}>! Expected was a BsonDocument instance.";
throw new IllegalArgumentException(MessageFormat.format(pattern, rawSnapshotEntity.getClass()));
}
/**
* Converts the specified BsonDocument to a {@link JsonObject}.
*
* @param bsonValue the BsonDocument to be converted.
* @return a JsonObject whose origin is {@code bsonValue}.
* @throws NullPointerException if {@code bsonValue} is {@code null}.
* @throws DittoJsonException if {@code bsonValue} cannot be serialized to a
* JsonObject.
*/
private static JsonObject convertToJson(final BsonValue bsonValue) {
checkNotNull(bsonValue, "BsonValue to be converted");
final DittoBsonJson dittoBsonJson = DittoBsonJson.getInstance();
final JsonObject jsonObject = dittoBsonJson.serialize(bsonValue).asObject();
return DittoJsonException.wrapJsonRuntimeException(() -> jsonObject);
}
@Nullable
private T tryToCreateJsonifiableFrom(final JsonObject jsonObject) {
try {
final var deletedLifecycleField = getDeletedLifecycleJsonField();
if (jsonObject.getValue(deletedLifecycleField.getKey())
.filter(deletedLifecycleField.getValue()::equals)
.isPresent()) {
return null; // entity is deleted
} else {
return createJsonifiableFrom(jsonObject);
}
} catch (final JsonParseException | DittoRuntimeException e) {
final String pattern = "Failed to deserialize JSON <{0}>!";
logger.error(MessageFormat.format(pattern, jsonObject), e);
return null;
}
}
/**
* Creates a Jsonifiable of type {@code T} from the specified JSON object.
*
* @param jsonObject a JSON Object representation of a Jsonifiable.
* @return the Jsonifiable which originates from {@code jsonObject}.
* @throws org.eclipse.ditto.json.JsonParseException if {@code jsonObject} does not have the correct format.
*/
protected abstract T createJsonifiableFrom(JsonObject jsonObject);
}