Skip to content

Commit e10bd98

Browse files
committed
Some cleanup and a few additional tests
1 parent 795a823 commit e10bd98

9 files changed

+110
-27
lines changed

src/main/java/com/unitvectory/crossfiresync/ConfigFirestoreSettings.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
package com.unitvectory.crossfiresync;
1515

1616
import lombok.Builder;
17-
import lombok.NonNull;
1817
import lombok.Value;
1918

2019
/**
@@ -26,13 +25,28 @@
2625
@Builder
2726
public class ConfigFirestoreSettings {
2827

28+
/**
29+
* The database name.
30+
*/
2931
private final String databaseName;
3032

31-
static ConfigFirestoreSettings build(@NonNull FirestoreChangeConfig config) {
33+
/**
34+
* Builds the Firestore settings from the Firestore change configuration.
35+
*
36+
* @param config the Firestore change configuration
37+
* @return the Firestore settings
38+
*/
39+
static ConfigFirestoreSettings build(FirestoreChangeConfig config) {
3240
return ConfigFirestoreSettings.builder().databaseName(config.getDatabaseName()).build();
3341
}
3442

35-
static ConfigFirestoreSettings build(@NonNull PubSubChangeConfig config) {
43+
/**
44+
* Builds the Firestore settings from the PubSub change configuration.
45+
*
46+
* @param config the PubSub change configuration
47+
* @return the Firestore settings
48+
*/
49+
static ConfigFirestoreSettings build(PubSubChangeConfig config) {
3650
return ConfigFirestoreSettings.builder().databaseName(config.getDatabaseName()).build();
3751
}
3852
}

src/main/java/com/unitvectory/crossfiresync/ConfigPublisherFactoryDefault.java

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ class ConfigPublisherFactoryDefault implements ConfigPublisherFactory {
2626

2727
@Override
2828
public Publisher getPublisher(ConfigPublisherSettings settings) throws IOException {
29-
3029
ProjectTopicName topicName =
3130
ProjectTopicName.of(settings.getProject(), settings.getTopic());
3231
return Publisher.newBuilder(topicName).setEnableMessageOrdering(true).build();

src/main/java/com/unitvectory/crossfiresync/ConfigPublisherSettings.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
package com.unitvectory.crossfiresync;
1515

1616
import lombok.Builder;
17-
import lombok.NonNull;
1817
import lombok.Value;
1918

2019
/**
@@ -26,11 +25,23 @@
2625
@Builder
2726
public class ConfigPublisherSettings {
2827

28+
/**
29+
* The GCP project.
30+
*/
2931
private final String project;
3032

33+
/**
34+
* The Pub/Sub topic.
35+
*/
3136
private final String topic;
3237

33-
static ConfigPublisherSettings build(@NonNull FirestoreChangeConfig config) {
38+
/**
39+
* Builds the publisher settings from the Firestore change configuration.
40+
*
41+
* @param config the Firestore change configuration
42+
* @return the publisher settings
43+
*/
44+
static ConfigPublisherSettings build(FirestoreChangeConfig config) {
3445
return ConfigPublisherSettings.builder().project(config.getProject())
3546
.topic(config.getTopic()).build();
3647
}

src/main/java/com/unitvectory/crossfiresync/CrossFireSync.java src/main/java/com/unitvectory/crossfiresync/CrossFireSyncAttributes.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
import lombok.experimental.UtilityClass;
1717

1818
/**
19-
* The CrossFireSync class.
19+
* The CrossFireSyncAttributes class.
2020
*
2121
* @author Jared Hatfield (UnitVectorY Labs)
2222
*/
2323
@UtilityClass
24-
class CrossFireSync {
24+
class CrossFireSyncAttributes {
2525

2626
/**
2727
* Name of the timestamp attribute

src/main/java/com/unitvectory/crossfiresync/FirestoreChangePublisher.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ public void accept(CloudEvent event) throws InvalidProtocolBufferException {
107107
}
108108

109109
// Check to see if this is a delete
110-
if (firestoreEventData.hasValue()
111-
&& firestoreEventData.getValue().containsFields(CrossFireSync.DELETE_FIELD)) {
110+
if (firestoreEventData.hasValue() && firestoreEventData.getValue()
111+
.containsFields(CrossFireSyncAttributes.DELETE_FIELD)) {
112112
// The delete field being present is the signal to delete the record in the
113113
// local region without publishing to the PubSub topic.
114114
DocumentReference documentReference = this.db.document(documentPath);
@@ -168,15 +168,15 @@ boolean shouldReplicate(DocumentEventData firestoreEventData) {
168168
Document document = firestoreEventData.getValue();
169169

170170
// Inserted without the database replication field, replicate it
171-
Value sourceValue =
172-
document.getFieldsOrDefault(CrossFireSync.SOURCE_DATABASE_FIELD, null);
171+
Value sourceValue = document
172+
.getFieldsOrDefault(CrossFireSyncAttributes.SOURCE_DATABASE_FIELD, null);
173173
if (sourceValue == null || !sourceValue.hasStringValue()) {
174174
return true;
175175
}
176176

177177
// Inserted without the timestamp replication field, replicate it
178178
Value timestampValue =
179-
document.getFieldsOrDefault(CrossFireSync.TIMESTAMP_FIELD, null);
179+
document.getFieldsOrDefault(CrossFireSyncAttributes.TIMESTAMP_FIELD, null);
180180
if (timestampValue == null || !timestampValue.hasTimestampValue()) {
181181
return true;
182182
}
@@ -190,23 +190,23 @@ boolean shouldReplicate(DocumentEventData firestoreEventData) {
190190
// There is no database replication field in the update, replicate it
191191
// It definitely want a replicated record
192192
Value newDatabase = firestoreEventData.getValue()
193-
.getFieldsOrDefault(CrossFireSync.SOURCE_DATABASE_FIELD, null);
193+
.getFieldsOrDefault(CrossFireSyncAttributes.SOURCE_DATABASE_FIELD, null);
194194
if (newDatabase == null || !newDatabase.hasStringValue()) {
195195
return true;
196196
}
197197

198198
// There is no new timestamp replication field in the update, replicate it
199199
// It definitely want a replicated record
200200
Value newTimestamp = firestoreEventData.getValue()
201-
.getFieldsOrDefault(CrossFireSync.TIMESTAMP_FIELD, null);
201+
.getFieldsOrDefault(CrossFireSyncAttributes.TIMESTAMP_FIELD, null);
202202
if (newTimestamp == null || !newTimestamp.hasTimestampValue()) {
203203
return true;
204204
}
205205

206206
Timestamp newTs = newTimestamp.getTimestampValue();
207207

208208
Value oldTimestamp = firestoreEventData.getOldValue()
209-
.getFieldsOrDefault(CrossFireSync.TIMESTAMP_FIELD, null);
209+
.getFieldsOrDefault(CrossFireSyncAttributes.TIMESTAMP_FIELD, null);
210210
Timestamp oldTs = null;
211211
if (oldTimestamp == null || !oldTimestamp.hasTimestampValue()) {
212212
// There is a new timestamp (previous check) but there was no old timestamp this
@@ -225,7 +225,8 @@ boolean shouldReplicate(DocumentEventData firestoreEventData) {
225225

226226
// Skip field that have the delete field set, these are not replicated to other
227227
// regions; only deleting in local region
228-
return !firestoreEventData.getOldValue().containsFields(CrossFireSync.DELETE_FIELD);
228+
return !firestoreEventData.getOldValue()
229+
.containsFields(CrossFireSyncAttributes.DELETE_FIELD);
229230
}
230231
}
231232

src/main/java/com/unitvectory/crossfiresync/PubSubChangeConfig.java

-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,4 @@ public class PubSubChangeConfig {
4444
@Builder.Default
4545
private final ConfigFirestoreFactory firestoreFactory = new ConfigFirestoreFactoryDefault();
4646

47-
48-
4947
}

src/main/java/com/unitvectory/crossfiresync/PubSubChangeConsumer.java

+27-8
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ public void accept(CloudEvent event) throws InvalidProtocolBufferException {
124124
// For cross region replication to work properly two additional attributes must
125125
// be written to the document to indicate what region the replicated attribute
126126
// came from and the timestamp of when the record was updated
127-
record.put(CrossFireSync.TIMESTAMP_FIELD, updatedTime);
128-
record.put(CrossFireSync.SOURCE_DATABASE_FIELD, pubsubDatabase);
127+
record.put(CrossFireSyncAttributes.TIMESTAMP_FIELD, updatedTime);
128+
record.put(CrossFireSyncAttributes.SOURCE_DATABASE_FIELD, pubsubDatabase);
129129

130130
// Perform the update
131131
this.updateTransaction(documentReference, updatedTime, record);
@@ -141,9 +141,9 @@ public void accept(CloudEvent event) throws InvalidProtocolBufferException {
141141
// Prepare the updates, set the deleted flag instead of actually deleting so the
142142
// delete in the remote regions will not redundantly cascade to other regions.
143143
Map<String, Object> updates = new HashMap<>();
144-
updates.put(CrossFireSync.TIMESTAMP_FIELD, deleteTimestamp);
145-
updates.put(CrossFireSync.DELETE_FIELD, true);
146-
updates.put(CrossFireSync.SOURCE_DATABASE_FIELD, pubsubDatabase);
144+
updates.put(CrossFireSyncAttributes.TIMESTAMP_FIELD, deleteTimestamp);
145+
updates.put(CrossFireSyncAttributes.DELETE_FIELD, true);
146+
updates.put(CrossFireSyncAttributes.SOURCE_DATABASE_FIELD, pubsubDatabase);
147147

148148
boolean flagged = this.deleteFlagTransaction(documentReference, updates);
149149

@@ -159,6 +159,15 @@ public void accept(CloudEvent event) throws InvalidProtocolBufferException {
159159
logger.info("Pub/Sub message: " + event);
160160
}
161161

162+
/**
163+
* Update a Firestore document with a transaction.
164+
*
165+
* @param documentReference The document reference
166+
* @param updatedTime The updated time
167+
* @param record The record to update
168+
* @throws InterruptedException If the transaction is interrupted
169+
* @throws ExecutionException If the transaction fails
170+
*/
162171
void updateTransaction(DocumentReference documentReference, Timestamp updatedTime,
163172
Map<String, Object> record) throws InterruptedException, ExecutionException {
164173
ApiFuture<Void> transaction = db.runTransaction((Transaction.Function<Void>) t -> {
@@ -171,9 +180,10 @@ void updateTransaction(DocumentReference documentReference, Timestamp updatedTim
171180
shouldWrite = true;
172181
} else {
173182
// Check if the timestamp is older or not present
174-
Timestamp existingTimestamp = snapshot.contains(CrossFireSync.TIMESTAMP_FIELD)
175-
? snapshot.getTimestamp(CrossFireSync.TIMESTAMP_FIELD)
176-
: null;
183+
Timestamp existingTimestamp =
184+
snapshot.contains(CrossFireSyncAttributes.TIMESTAMP_FIELD)
185+
? snapshot.getTimestamp(CrossFireSyncAttributes.TIMESTAMP_FIELD)
186+
: null;
177187
if (existingTimestamp == null || updatedTime.compareTo(existingTimestamp) > 0) {
178188
shouldWrite = true;
179189
}
@@ -191,6 +201,15 @@ void updateTransaction(DocumentReference documentReference, Timestamp updatedTim
191201
transaction.get();
192202
}
193203

204+
/**
205+
* Flag a Firestore document for deletion with a transaction.
206+
*
207+
* @param documentReference The document reference
208+
* @param updates The updates to apply
209+
* @return True if the document was flagged for deletion
210+
* @throws InterruptedException If the transaction is interrupted
211+
* @throws ExecutionException If the transaction fails
212+
*/
194213
boolean deleteFlagTransaction(DocumentReference documentReference, Map<String, Object> updates)
195214
throws InterruptedException, ExecutionException {
196215
ApiFuture<Boolean> transaction = db.runTransaction((Transaction.Function<Boolean>) t -> {

src/main/java/com/unitvectory/crossfiresync/PubSubMessage.java

+6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ class PubSubMessage {
3737

3838
private String publishTime;
3939

40+
/**
41+
* Gets the attribute value.
42+
*
43+
* @param name the attribute name
44+
* @return the attribute value
45+
*/
4046
public String getAttribute(String name) {
4147
if (this.attributes == null) {
4248
return null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package com.unitvectory.crossfiresync;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import org.junit.jupiter.api.Test;
18+
19+
/**
20+
* The ReplicationMode test class.
21+
*
22+
* @author Jared Hatfield (UnitVectorY Labs)
23+
*/
24+
public class ReplicationModeTest {
25+
26+
@Test
27+
public void nullTest() {
28+
assertEquals(ReplicationMode.NONE, ReplicationMode.parseDefaultNone(null));
29+
}
30+
31+
@Test
32+
public void invalidTest() {
33+
assertEquals(ReplicationMode.NONE, ReplicationMode.parseDefaultNone("invalid"));
34+
}
35+
}

0 commit comments

Comments
 (0)