Skip to content

Commit 6ac0c34

Browse files
committed
Fix auditing immutable entities
Signed-off-by: Artur Kalimullin <kalimullin@gmail.com>
1 parent 8f88670 commit 6ac0c34

10 files changed

Lines changed: 296 additions & 19 deletions

File tree

src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEntityCallback.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* {@link EntityCallback} to populate auditing related fields on an entity about to be saved.
3131
*
3232
* @author Jorge Rodríguez Martín
33+
* @author Artur Kalimullin
3334
* @since 4.2
3435
*/
3536
public class AuditingEntityCallback implements BeforeConvertCallback<Object>, AfterConvertCallback<Object>, Ordered {
@@ -54,9 +55,7 @@ public AuditingEntityCallback(ObjectFactory<IsNewAwareAuditingHandler> auditingH
5455
*/
5556
@Override
5657
public Object onBeforeConvert(Object entity, String collection) {
57-
// LOG.trace("onBeforeConvert " + entity);
58-
return entity; // markAudited called in AuditingEventListener.onApplicationEvent()
59-
// auditingHandlerFactory.getObject().markAudited(entity);
58+
return auditingHandlerFactory.getObject().markAudited(entity);
6059
}
6160

6261
/*

src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.springframework.data.couchbase.config.BeanNames;
3333
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
3434
import org.springframework.data.couchbase.core.mapping.event.AuditingEntityCallback;
35-
import org.springframework.data.couchbase.core.mapping.event.AuditingEventListener;
3635
import org.springframework.util.Assert;
3736

3837
/**
@@ -44,6 +43,7 @@
4443
* @author Simon Baslé
4544
* @author Michael Reiche
4645
* @author Jorge Rodríguez Martín
46+
* @author Artur Kalimullin
4747
*/
4848
public class CouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
4949

@@ -85,8 +85,6 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle
8585
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
8686
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
8787

88-
// Register the AuditEntityCallback
89-
9088
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
9189
.rootBeanDefinition(AuditingEntityCallback.class);
9290
listenerBeanDefinitionBuilder
@@ -95,16 +93,6 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle
9593
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
9694
AuditingEntityCallback.class.getName(), registry);
9795

98-
// Register the AuditingEventListener
99-
100-
BeanDefinitionBuilder listenerBeanDefinitionBuilder2 = BeanDefinitionBuilder
101-
.rootBeanDefinition(AuditingEventListener.class);
102-
listenerBeanDefinitionBuilder2
103-
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
104-
105-
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder2.getBeanDefinition(),
106-
AuditingEventListener.class.getName(), registry);
107-
10896
}
10997

11098
private void ensureMappingContext(BeanDefinitionRegistry registry, Object source) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.core.mapping.event;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
import static org.junit.jupiter.api.Assertions.assertNotSame;
20+
21+
import java.time.Instant;
22+
import java.util.Optional;
23+
24+
import org.junit.jupiter.api.Test;
25+
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
26+
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
27+
import org.springframework.data.couchbase.domain.AuditedRecord;
28+
29+
/**
30+
* @author Artur Kalimullin
31+
*/
32+
class AuditingEntityCallbackTests {
33+
34+
@Test
35+
void returnsAuditedImmutableEntity() {
36+
Instant now = Instant.parse("2026-08-12T09:00:00Z");
37+
IsNewAwareAuditingHandler auditingHandler = IsNewAwareAuditingHandler.from(new CouchbaseMappingContext());
38+
auditingHandler.setDateTimeProvider(() -> Optional.of(now));
39+
40+
AuditedRecord original = new AuditedRecord("id", 0, null, null, "value");
41+
AuditingEntityCallback callback = new AuditingEntityCallback(() -> auditingHandler);
42+
AuditedRecord audited = (AuditedRecord) callback.onBeforeConvert(original, "collection");
43+
44+
assertNotSame(original, audited);
45+
assertEquals(now, audited.createdDate());
46+
assertEquals(now, audited.lastModifiedDate());
47+
}
48+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.domain;
17+
18+
import java.time.Instant;
19+
20+
import org.springframework.data.annotation.CreatedDate;
21+
import org.springframework.data.annotation.Id;
22+
import org.springframework.data.annotation.LastModifiedDate;
23+
import org.springframework.data.annotation.Version;
24+
import org.springframework.data.couchbase.core.mapping.Document;
25+
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
26+
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
27+
28+
/**
29+
* @author Artur Kalimullin
30+
*/
31+
@Document
32+
public class AuditedImmutableEntity {
33+
34+
@Id
35+
@GeneratedValue(strategy = GenerationStrategy.UNIQUE) private final String id;
36+
@Version private final long version;
37+
@CreatedDate private final Instant createdDate;
38+
@LastModifiedDate private final Instant lastModifiedDate;
39+
private final String value;
40+
41+
public AuditedImmutableEntity(String id, long version, Instant createdDate, Instant lastModifiedDate, String value) {
42+
this.id = id;
43+
this.version = version;
44+
this.createdDate = createdDate;
45+
this.lastModifiedDate = lastModifiedDate;
46+
this.value = value;
47+
}
48+
49+
public String getId() {
50+
return id;
51+
}
52+
53+
public long getVersion() {
54+
return version;
55+
}
56+
57+
public Instant getCreatedDate() {
58+
return createdDate;
59+
}
60+
61+
public Instant getLastModifiedDate() {
62+
return lastModifiedDate;
63+
}
64+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.domain;
17+
18+
import org.springframework.data.repository.CrudRepository;
19+
20+
/**
21+
* @author Artur Kalimullin
22+
*/
23+
public interface AuditedImmutableEntityRepository extends CrudRepository<AuditedImmutableEntity, String> {}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.domain;
17+
18+
import java.time.Instant;
19+
20+
import org.springframework.data.annotation.CreatedDate;
21+
import org.springframework.data.annotation.Id;
22+
import org.springframework.data.annotation.LastModifiedDate;
23+
import org.springframework.data.annotation.Version;
24+
import org.springframework.data.couchbase.core.mapping.Document;
25+
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
26+
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
27+
28+
/**
29+
* @author Artur Kalimullin
30+
*/
31+
@Document
32+
public record AuditedRecord(@Id @GeneratedValue(strategy = GenerationStrategy.UNIQUE) String id, @Version long version,
33+
@CreatedDate Instant createdDate, @LastModifiedDate Instant lastModifiedDate, String value) {
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.domain;
17+
18+
import org.springframework.data.repository.CrudRepository;
19+
20+
/**
21+
* @author Artur Kalimullin
22+
*/
23+
public interface AuditedRecordRepository extends CrudRepository<AuditedRecord, String> {}

src/test/java/org/springframework/data/couchbase/domain/Config.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.springframework.cache.annotation.EnableCaching;
2222
import org.springframework.context.annotation.Bean;
2323
import org.springframework.context.annotation.Configuration;
24-
import org.springframework.data.auditing.DateTimeProvider;
2524
import org.springframework.data.couchbase.CouchbaseClientFactory;
2625
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
2726
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
@@ -51,6 +50,7 @@
5150
* @author Michael Nitschinger
5251
* @author Michael Reiche
5352
* @author Jorge Rodriguez Martin
53+
* @author Artur Kalimullin
5454
* @since 3.0
5555
*/
5656
@Configuration
@@ -128,7 +128,7 @@ public ReactiveNaiveAuditorAware testReactiveAuditorAware() {
128128
}
129129

130130
@Bean(name = "dateTimeProviderRef")
131-
public DateTimeProvider testDateTimeProvider() {
131+
public AuditingDateTimeProvider testDateTimeProvider() {
132132
return new AuditingDateTimeProvider();
133133
}
134134

@@ -211,7 +211,8 @@ public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingConte
211211
// that has an getAliasFor(info) that just returns getType().getName().
212212
// Our CustomMappingCouchbaseConverter uses a TypeBasedCouchbaseTypeMapper that will
213213
// use the DocumentType annotation
214-
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext, typeKey(), couchbaseCustomConversions);
214+
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext, typeKey(),
215+
couchbaseCustomConversions);
215216
return converter;
216217
}
217218

src/test/java/org/springframework/data/couchbase/domain/time/AuditingDateTimeProvider.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
import org.springframework.data.auditing.DateTimeProvider;
2323

24+
/**
25+
* @author Artur Kalimullin
26+
*/
2427
public class AuditingDateTimeProvider implements DateTimeProvider {
2528

2629
private DateTimeService dateTimeService = new FixedDateTimeService();
@@ -31,6 +34,10 @@ public AuditingDateTimeProvider(DateTimeService dateTimeService) {
3134
this.dateTimeService = dateTimeService;
3235
}
3336

37+
public void setDateTimeService(DateTimeService dateTimeService) {
38+
this.dateTimeService = dateTimeService;
39+
}
40+
3441
@Override
3542
public Optional<TemporalAccessor> getNow() {
3643
return Optional.of(Instant.ofEpochSecond(dateTimeService.getCurrentDateAndTime().toEpochSecond()));

0 commit comments

Comments
 (0)