Skip to content

Commit f72c0ab

Browse files
committed
Merge remote-tracking branch 'origin/7.0.x' into 1294
2 parents 661249d + d7c213d commit f72c0ab

File tree

12 files changed

+720
-195
lines changed

12 files changed

+720
-195
lines changed

.dependabot/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 1
2+
update_configs:
3+
- package_manager: "java:gradle"
4+
directory: "/"
5+
update_schedule: "daily"
6+
default_labels:
7+
- "type: dependency-upgrade"
8+
target_branch: "7.0.x"

grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/ConfigurationBuilder.groovy

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ abstract class ConfigurationBuilder<B, C> {
108108
protected abstract C toConfiguration(B builder)
109109

110110
private C buildInternal(B builder, String startingPrefix) {
111-
buildRecurse(builder, this.fallBackConfiguration, startingPrefix)
111+
buildRecurse(builder, new ArrayList<Class>(), this.fallBackConfiguration, startingPrefix)
112112

113113
return toConfiguration(builder)
114114
}
@@ -125,7 +125,14 @@ abstract class ConfigurationBuilder<B, C> {
125125
return classes.reverse()
126126
}
127127

128+
/**
129+
* @deprecated use {@link ConfigurationBuilder#buildRecurse(Object, List, Object, String)} instead
130+
*/
128131
protected void buildRecurse(Object builder, Object fallBackConfig, String startingPrefix) {
132+
buildRecurse(builder, new ArrayList<Class>(), fallBackConfig, startingPrefix)
133+
}
134+
135+
protected void buildRecurse(Object builder, List<Class> builderQueue, Object fallBackConfig, String startingPrefix) {
129136

130137
List<Class> hierarchy = toHierarchy(builder.getClass())
131138

@@ -184,14 +191,23 @@ abstract class ConfigurationBuilder<B, C> {
184191
newChildBuilder(newBuilder, propertyPath)
185192

186193
Object fallBackChildConfig = getFallBackValue(fallBackConfig, settingName)
187-
buildRecurse(newBuilder, fallBackChildConfig, propertyPath)
188-
189-
def buildMethod = ReflectionUtils.findMethod(newBuilder.getClass(), 'build')
190-
if (buildMethod != null) {
191-
method.invoke(builder, buildMethod.invoke(newBuilder))
192-
} else {
193-
method.invoke(builder, newBuilder)
194+
if (!builderQueue.contains(newBuilder.class)) {
195+
builderQueue.add(newBuilder.class)
196+
buildRecurse(newBuilder, builderQueue, fallBackChildConfig, propertyPath)
197+
builderQueue.remove(newBuilder.class)
198+
199+
def buildMethod = ReflectionUtils.findMethod(newBuilder.getClass(), 'build')
200+
if (buildMethod != null) {
201+
try {
202+
method.invoke(builder, buildMethod.invoke(newBuilder))
203+
} catch(Throwable e) {
204+
log.error("build method threw exception", e)
205+
}
206+
} else {
207+
method.invoke(builder, newBuilder)
208+
}
194209
}
210+
195211
continue
196212
}
197213

@@ -206,9 +222,13 @@ abstract class ConfigurationBuilder<B, C> {
206222
if (newBuilder != null) {
207223
Object fallBackChildConfig = getFallBackValue(fallBackConfig, settingName)
208224
newBuilder = newChildBuilderForFallback(newBuilder, fallBackChildConfig)
209-
buildRecurse(newBuilder, fallBackChildConfig, propertyPath)
210-
newChildBuilder(newBuilder, propertyPath)
211-
method.invoke(builder, newBuilder)
225+
if (!builderQueue.contains(newBuilder.class)) {
226+
builderQueue.add(newBuilder.class)
227+
buildRecurse(newBuilder, builderQueue, fallBackChildConfig, propertyPath)
228+
builderQueue.remove(newBuilder.class)
229+
newChildBuilder(newBuilder, propertyPath)
230+
method.invoke(builder, newBuilder)
231+
}
212232
continue
213233
}
214234
}
@@ -235,8 +255,12 @@ abstract class ConfigurationBuilder<B, C> {
235255
newChildBuilder(newBuilder, propertyPath)
236256

237257
Object fallBackChildConfig = getFallBackValue(fallBackConfig, methodName)
238-
buildRecurse(newBuilder, fallBackChildConfig, propertyPath)
239-
method.invoke(builder, newBuilder)
258+
if (!builderQueue.contains(newBuilder.class)) {
259+
builderQueue.add(newBuilder.class)
260+
buildRecurse(newBuilder, builderQueue, fallBackChildConfig, propertyPath)
261+
builderQueue.remove(newBuilder.class)
262+
method.invoke(builder, newBuilder)
263+
}
240264
continue
241265
}
242266

@@ -283,7 +307,11 @@ abstract class ConfigurationBuilder<B, C> {
283307
}
284308

285309
String getterPropertyPath = startingPrefix ? "${startingPrefix}.${NameUtils.getPropertyNameForGetterOrSetter(methodName)}" : NameUtils.getPropertyNameForGetterOrSetter(methodName)
286-
buildRecurse(childBuilder, fallBackChildConfig, getterPropertyPath)
310+
if (!builderQueue.contains(childBuilder.class)) {
311+
builderQueue.add(childBuilder.class)
312+
buildRecurse(childBuilder, builderQueue, fallBackChildConfig, getterPropertyPath)
313+
builderQueue.remove(childBuilder.class)
314+
}
287315
continue
288316
}
289317
}

grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/dirty/checking/DirtyCheckable.groovy

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ trait DirtyCheckable {
2626
$changedProperties = new LinkedHashMap<String, Object>()
2727
}
2828

29+
/**
30+
* Sync the changes for a given instance with this instance.
31+
*
32+
* @param o a given object
33+
*/
34+
void syncChangedProperties(Object o) {
35+
if (o instanceof DirtyCheckable) {
36+
o.trackChanges($changedProperties)
37+
}
38+
}
39+
40+
/**
41+
* Initialises the changes with the given changes.
42+
*
43+
* @param changedProperties The changes.
44+
*/
45+
void trackChanges(Map<String, Object> changedProperties) {
46+
$changedProperties = changedProperties
47+
}
48+
2949
/**
3050
* @return True if the instance has any changes
3151
*/

grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/proxy/AssociationQueryProxyHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.grails.datastore.mapping.proxy;
1717

1818
import org.grails.datastore.mapping.core.Session;
19+
import org.grails.datastore.mapping.dirty.checking.DirtyCheckable;
1920
import org.grails.datastore.mapping.engine.AssociationQueryExecutor;
2021
import org.grails.datastore.mapping.reflect.FieldEntityAccess;
2122
import org.springframework.cglib.reflect.FastClass;
@@ -80,6 +81,9 @@ protected Object resolveDelegate(Object self) {
8081
if( target == null ) {
8182
throw new DataIntegrityViolationException("Proxy for ["+ proxyClass.getName()+"] for association ["+executor.getIndexedEntity().getName()+"] could not be initialized");
8283
}
84+
if (target instanceof DirtyCheckable) {
85+
((DirtyCheckable) target).syncChangedProperties(self);
86+
}
8387
}
8488
return target;
8589
}

grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/proxy/SessionEntityProxyMethodHandler.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.grails.datastore.mapping.proxy;
1717

1818
import org.grails.datastore.mapping.core.Session;
19+
import org.grails.datastore.mapping.dirty.checking.DirtyCheckable;
1920
import org.grails.datastore.mapping.model.PersistentEntity;
2021
import org.grails.datastore.mapping.reflect.FieldEntityAccess;
2122
import org.slf4j.Logger;
@@ -54,7 +55,7 @@ public SessionEntityProxyMethodHandler(Class proxyClass, Session session, Class
5455
@Override
5556
protected Object resolveDelegate(Object self) {
5657
if (target == null) {
57-
initializeTarget();
58+
initializeTarget(self);
5859

5960
// This tends to happen during unit testing if the proxy class is not properly mocked
6061
// and therefore can't be found in the session.
@@ -72,6 +73,13 @@ protected void initializeTarget() {
7273
target = session.retrieve(cls, id);
7374
}
7475

76+
protected void initializeTarget(Object self) {
77+
initializeTarget();
78+
if (target instanceof DirtyCheckable) {
79+
((DirtyCheckable) target).syncChangedProperties(self);
80+
}
81+
}
82+
7583
@Override
7684
protected Object isProxyInitiated(Object self) {
7785
return target != null;
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package org.grails.datastore.mapping.config
2+
3+
import groovy.transform.AutoClone
4+
import groovy.transform.builder.Builder
5+
import groovy.transform.builder.SimpleStrategy
6+
import org.grails.datastore.mapping.core.DatastoreUtils
7+
import org.springframework.core.env.PropertyResolver
8+
import spock.lang.Specification
9+
10+
class MandatoryFieldsInConfigurationBuilderSpec extends Specification {
11+
12+
void "mandatory fields in optional child builders don't stop configuration"() {
13+
given:
14+
PropertyResolver config = DatastoreUtils.createPropertyResolver([:])
15+
16+
when:
17+
MongoConnectionSourceSettingsBuilder builder = new MongoConnectionSourceSettingsBuilder(config, "grails.mongodb")
18+
builder.build()
19+
20+
then:
21+
noExceptionThrown()
22+
}
23+
24+
void "if you supply mandatory fields via configuration the builder uses them"() {
25+
given:
26+
PropertyResolver config = DatastoreUtils.createPropertyResolver([
27+
"grails.mongodb.options.autoEncryptionSettings.bypassAutoEncryption": true,
28+
"grails.mongodb.options.autoEncryptionSettings.keyVaultNamespace": false
29+
])
30+
31+
when:
32+
MongoConnectionSourceSettingsBuilder builder = new MongoConnectionSourceSettingsBuilder(config, "grails.mongodb")
33+
MongoConnectionSourceSettings settings = builder.build()
34+
35+
then:
36+
noExceptionThrown()
37+
settings.options.autoEncryptionSettings.bypassAutoEncryption
38+
}
39+
40+
class MongoConnectionSourceSettingsBuilder extends ConfigurationBuilder<MongoConnectionSourceSettings, MongoConnectionSourceSettings>{
41+
42+
MongoConnectionSourceSettingsBuilder(PropertyResolver propertyResolver, String configurationPrefix, Object fallBackConfiguration, String builderMethodPrefix) {
43+
super(propertyResolver, configurationPrefix, fallBackConfiguration, builderMethodPrefix)
44+
}
45+
46+
MongoConnectionSourceSettingsBuilder(PropertyResolver propertyResolver, String configurationPrefix, Object fallBackConfiguration) {
47+
super(propertyResolver, configurationPrefix, fallBackConfiguration)
48+
}
49+
50+
MongoConnectionSourceSettingsBuilder(PropertyResolver propertyResolver, String configurationPrefix) {
51+
super(propertyResolver, configurationPrefix)
52+
}
53+
54+
MongoConnectionSourceSettingsBuilder(PropertyResolver propertyResolver, String configurationPrefix, String builderMethodPrefix) {
55+
super(propertyResolver, configurationPrefix, builderMethodPrefix)
56+
}
57+
58+
@Override
59+
protected MongoConnectionSourceSettings createBuilder() {
60+
new MongoConnectionSourceSettings()
61+
}
62+
63+
@Override
64+
protected MongoConnectionSourceSettings toConfiguration(MongoConnectionSourceSettings builder) {
65+
builder
66+
}
67+
}
68+
69+
@AutoClone
70+
@Builder(builderStrategy = SimpleStrategy, prefix = '')
71+
static class MongoConnectionSourceSettings {
72+
MongoClientOptions.Builder options = MongoClientOptions.builder()
73+
74+
}
75+
76+
static class MongoClientOptions {
77+
78+
private AutoEncryptionSettings autoEncryptionSettings
79+
80+
private MongoClientOptions(Builder builder) {
81+
autoEncryptionSettings = builder.autoEncryptionSettings;
82+
}
83+
84+
static Builder builder() {
85+
new Builder()
86+
}
87+
88+
static class Builder {
89+
90+
private AutoEncryptionSettings autoEncryptionSettings
91+
92+
Builder autoEncryptionSettings(AutoEncryptionSettings autoEncryptionSettings) {
93+
this.autoEncryptionSettings = autoEncryptionSettings
94+
this
95+
}
96+
97+
MongoClientOptions build() {
98+
new MongoClientOptions(this)
99+
}
100+
}
101+
}
102+
103+
104+
static class AutoEncryptionSettings {
105+
private boolean bypassAutoEncryption
106+
private String keyVaultNamespace
107+
108+
private AutoEncryptionSettings(Builder builder) {
109+
this.bypassAutoEncryption = builder.bypassAutoEncryption
110+
this.keyVaultNamespace = notNull("keyVaultNamespace", builder.keyVaultNamespace)
111+
}
112+
113+
static Builder builder() {
114+
new Builder()
115+
}
116+
117+
static String notNull(String name, Object value) {
118+
if (value == null) {
119+
throw new IllegalArgumentException(name + " can not be null");
120+
}
121+
value
122+
}
123+
124+
static class Builder {
125+
private boolean bypassAutoEncryption
126+
private String keyVaultNamespace
127+
128+
private Builder() {
129+
}
130+
131+
Builder keyVaultNamespace(String keyVaultNamespace) {
132+
this.keyVaultNamespace = notNull("keyVaultNamespace", keyVaultNamespace)
133+
return this
134+
}
135+
136+
Builder bypassAutoEncryption(boolean bypassAutoEncryption) {
137+
this.bypassAutoEncryption = bypassAutoEncryption
138+
this
139+
}
140+
141+
AutoEncryptionSettings build() {
142+
new AutoEncryptionSettings(this)
143+
}
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)