Skip to content

Commit bb3951b

Browse files
committed
Merge branch '7.0.x' into 7.0.x-autotimestamp-enhancements
2 parents da8776c + 0de145d commit bb3951b

File tree

13 files changed

+98
-28
lines changed

13 files changed

+98
-28
lines changed

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
# limitations under the License.
1515

1616
contact_links:
17+
- name: Grails Mailing Lists
18+
url: https://grails.apache.org/community.html
19+
about: Ask questions on the Mailing Lists
1720
- name: Grails Core Discussions
1821
url: https://github.com/apache/grails-core/discussions
19-
about: Ask questions about Grails® framework on Github
20-
- name: Stack Overflow
21-
url: https://stackoverflow.com/tags/grails
22-
about: Ask questions on Stack Overflow
22+
about: Discuss Grails® framework on GitHub
2323
- name: Grails Slack
2424
url: https://grails.slack.com/
25-
about: Chat with us on Grails Community Slack.
25+
about: Chat with us on Grails Community Slack.

dependencies.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ ext {
3838
'jquery.version' : '3.7.1',
3939
'objenesis.version' : '3.4',
4040
'gradle-spock.version' : '2.3-groovy-3.0',
41-
'spring-boot.version' : '3.5.6',
41+
'spring-boot.version' : '3.5.7',
4242
]
4343

4444
// Note: the name of the dependency must be the prefix of the property name so properties in the pom are resolved correctly
@@ -73,7 +73,7 @@ ext {
7373
'bootstrap.version' : '5.3.7',
7474
'commons-codec.version' : '1.18.0',
7575
'geb-spock.version' : '8.0.0',
76-
'groovy.version' : '4.0.28',
76+
'groovy.version' : '4.0.29',
7777
'h2.version' : '2.3.232',
7878
'jackson.version' : '2.19.1',
7979
'jquery.version' : '3.7.1',

grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import jakarta.persistence.FlushModeType;
3232

3333
import com.mongodb.MongoClientSettings;
34+
import com.mongodb.MongoCommandException;
3435
import com.mongodb.client.MongoClient;
3536
import com.mongodb.client.MongoIterable;
3637
import com.mongodb.client.model.IndexOptions;
@@ -846,7 +847,11 @@ protected void initializeIndices(final PersistentEntity entity) {
846847
for (MongoCollection.Index index : indices) {
847848
final Map<String, Object> options = index.getOptions();
848849
final IndexOptions indexOptions = MongoConstants.mapToObject(IndexOptions.class, options);
849-
collection.createIndex(new Document(index.getDefinition()), indexOptions);
850+
try {
851+
collection.createIndex(new Document(index.getDefinition()), indexOptions);
852+
} catch (MongoCommandException e) {
853+
LOG.error("Failed to create index for entity [" + entity.getName() + "] with definition [" + index.getDefinition() + "]: " + e.getMessage(), e);
854+
}
850855
}
851856

852857
for (Map compoundIndex : mappedForm.getCompoundIndices()) {
@@ -859,11 +864,15 @@ protected void initializeIndices(final PersistentEntity entity) {
859864
}
860865
}
861866
Document indexDef = new Document(compoundIndex);
862-
if (indexAttributes != null) {
863-
final IndexOptions indexOptions = MongoConstants.mapToObject(IndexOptions.class, indexAttributes);
864-
collection.createIndex(indexDef, indexOptions);
865-
} else {
866-
collection.createIndex(indexDef);
867+
try {
868+
if (indexAttributes != null) {
869+
final IndexOptions indexOptions = MongoConstants.mapToObject(IndexOptions.class, indexAttributes);
870+
collection.createIndex(indexDef, indexOptions);
871+
} else {
872+
collection.createIndex(indexDef);
873+
}
874+
} catch (MongoCommandException e) {
875+
LOG.error("Failed to create compound index for entity [" + entity.getName() + "] with definition [" + indexDef + "]: " + e.getMessage(), e);
867876
}
868877
}
869878
}
@@ -889,11 +898,15 @@ protected void initializeIndices(final PersistentEntity entity) {
889898
}
890899
}
891900
// continue using deprecated method to support older versions of MongoDB
892-
if (options.isEmpty()) {
893-
collection.createIndex(dbObject);
894-
} else {
895-
final IndexOptions indexOptions = MongoConstants.mapToObject(IndexOptions.class, options);
896-
collection.createIndex(dbObject, indexOptions);
901+
try {
902+
if (options.isEmpty()) {
903+
collection.createIndex(dbObject);
904+
} else {
905+
final IndexOptions indexOptions = MongoConstants.mapToObject(IndexOptions.class, options);
906+
collection.createIndex(dbObject, indexOptions);
907+
}
908+
} catch (MongoCommandException e) {
909+
LOG.error("Failed to create index for entity [" + entity.getName() + "] on property [" + property.getName() + "]: " + e.getMessage(), e);
897910
}
898911
}
899912
}

grails-databinding/src/main/groovy/org/grails/databinding/converters/DefaultConvertersConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ ValueConverter instantValueConverter() {
177177
return jsr310ConvertersConfiguration.instantValueConverter();
178178
}
179179

180+
@Bean("instantStructuredBindingEditor")
181+
TypedStructuredBindingEditor instantStructuredBindingEditor() {
182+
return jsr310ConvertersConfiguration.instantStructuredBindingEditor();
183+
}
184+
180185
@Bean("defaultUUIDConverter")
181186
protected UUIDConverter defaultuuidConverter() {
182187
return new UUIDConverter();

grails-databinding/src/main/groovy/org/grails/databinding/converters/Jsr310ConvertersConfiguration.groovy

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,21 @@ class Jsr310ConvertersConfiguration {
388388
}
389389
}
390390

391+
@Bean
392+
TypedStructuredBindingEditor instantStructuredBindingEditor() {
393+
new CustomDateBindingEditor<Instant>() {
394+
@Override
395+
Instant getDate(Calendar c) {
396+
c.toInstant()
397+
}
398+
399+
@Override
400+
Class<?> getTargetType() {
401+
Instant
402+
}
403+
}
404+
}
405+
391406
abstract class Jsr310DateValueConverter<T> implements ValueConverter {
392407

393408
@Override

grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ package grails.plugin.formfields
2020

2121
import java.sql.Blob
2222
import java.text.NumberFormat
23+
import java.time.Instant
2324
import java.time.LocalDate
2425
import java.time.LocalDateTime
26+
import java.time.LocalTime
27+
import java.time.OffsetDateTime
28+
import java.time.ZonedDateTime
2529

2630
import groovy.transform.CompileStatic
2731
import groovy.util.logging.Slf4j
@@ -726,7 +730,7 @@ class FormFieldsTagLib {
726730
}
727731

728732
// TODO: https://github.com/apache/grails-core/issues/14198
729-
boolean datePicker = model.type in [Date, Calendar, java.sql.Date, java.sql.Time, LocalDate, LocalDateTime]
733+
boolean datePicker = model.type in [Date, Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, LocalDate, LocalDateTime, Instant, ZonedDateTime, OffsetDateTime]
730734
if (!datePicker) {
731735
attrs.remove('selectDateClass')
732736
}
@@ -948,13 +952,22 @@ class FormFieldsTagLib {
948952
case Boolean:
949953
g.formatBoolean(boolean: model.value)
950954
break
951-
case Calendar:
952-
case Date:
955+
case LocalDate:
953956
case java.sql.Date:
957+
g.formatDate(date: model.value, type: 'DATE')
958+
break
954959
case java.sql.Time:
955-
case LocalDate:
960+
case LocalTime:
961+
g.formatDate(date: model.value, type: 'TIME')
962+
break
963+
case Calendar:
964+
case Date:
956965
case LocalDateTime:
957-
g.formatDate(date: model.value)
966+
case java.sql.Timestamp:
967+
case Instant:
968+
case ZonedDateTime:
969+
case OffsetDateTime:
970+
g.formatDate(date: model.value, type: 'DATETIME')
958971
break
959972
default:
960973
g.fieldValue(bean: model.bean, field: model.property)

grails-fields/src/test/groovy/grails/plugin/formfields/DisplayWidgetSpec.groovy

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package grails.plugin.formfields
2020

21+
import grails.plugin.formfields.mock.Cyborg
2122
import grails.plugin.formfields.mock.Person
2223
import grails.plugin.formfields.taglib.AbstractFormFieldsTagLibSpec
2324
import grails.testing.web.taglib.TagLibUnitTest
@@ -27,7 +28,7 @@ class DisplayWidgetSpec extends AbstractFormFieldsTagLibSpec implements TagLibUn
2728
def mockFormFieldsTemplateService = Mock(FormFieldsTemplateService)
2829

2930
def setupSpec() {
30-
mockDomain(Person)
31+
mockDomains(Person, Cyborg)
3132
}
3233

3334
def setup() {
@@ -37,7 +38,17 @@ class DisplayWidgetSpec extends AbstractFormFieldsTagLibSpec implements TagLibUn
3738

3839
void 'f:displayWidget without template and a date value renders the formatted date'() {
3940
expect:
40-
applyTemplate('<f:displayWidget bean="personInstance" property="dateOfBirth"/>', [personInstance: personInstance]) == applyTemplate('<g:formatDate date="${personInstance.dateOfBirth}"/>', [personInstance: personInstance])
41+
applyTemplate('<f:displayWidget bean="personInstance" property="dateOfBirth"/>', [personInstance: personInstance]) == applyTemplate('<g:formatDate date="${personInstance.dateOfBirth}" type="DATETIME"/>', [personInstance: personInstance])
42+
}
43+
44+
void 'f:displayWidget without template and an instant value renders the formatted date'() {
45+
expect:
46+
applyTemplate('<f:displayWidget bean="cyborgInstance" property="timestamp"/>', [cyborgInstance: cyborgInstance]) == applyTemplate('<g:formatDate date="${cyborgInstance.timestamp}" type="DATETIME"/>', [cyborgInstance: cyborgInstance])
47+
}
48+
49+
void 'f:displayWidget without template and a LocalDate value renders the formatted date'() {
50+
expect:
51+
applyTemplate('<f:displayWidget bean="cyborgInstance" property="birthDate"/>', [cyborgInstance: cyborgInstance]) == applyTemplate('<g:formatDate date="${cyborgInstance.birthDate}" type="DATE"/>', [cyborgInstance: cyborgInstance])
4152
}
4253

4354
void 'f:displayWidget without template and a boolean value renders the formatted boolean'() {

grails-fields/src/test/groovy/grails/plugin/formfields/mock/Person.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@
1818
*/
1919
package grails.plugin.formfields.mock
2020

21+
import java.time.Instant
22+
import java.time.LocalDate
23+
2124
import grails.gorm.annotation.AutoTimestamp
2225
import grails.persistence.Entity
2326

2427
@Entity
2528
class Cyborg extends Person {
2629
@AutoTimestamp(AutoTimestamp.EventType.CREATED) Date created
2730
@AutoTimestamp Date modified
31+
Instant timestamp
32+
LocalDate birthDate
2833
}
2934

3035
@Entity

grails-fields/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
package grails.plugin.formfields.taglib
2121

22+
import java.time.Instant
23+
import java.time.LocalDate
24+
2225
import grails.core.support.proxy.DefaultProxyHandler
2326
import grails.plugin.formfields.BeanPropertyAccessorFactory
2427
import grails.plugin.formfields.FieldsGrailsPlugin
@@ -45,7 +48,7 @@ abstract class AbstractFormFieldsTagLibSpec extends Specification implements Gra
4548
personInstance.address = new Address(street: "94 Evergreen Terrace", city: "Springfield", country: "USA")
4649
personInstance.emails = [home: "[email protected]", school: "[email protected]"]
4750
productInstance = new Product(netPrice: 12.33, name: "<script>alert('XSS');</script>")
48-
cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null)
51+
cyborgInstance = new Cyborg(name: "Hal", password: "monolith", gender: null, timestamp: Instant.parse("2025-10-16T00:12:15.195Z"), birthDate: LocalDate.of(2025, 10, 15))
4952
}
5053

5154
def cleanup() {

grails-fields/src/test/groovy/grails/plugin/formfields/taglib/DisplayTagSpec.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class DisplayTagSpec extends AbstractFormFieldsTagLibSpec implements TagLibUnitT
9898

9999
void 'renders date values using g:formatDate'() {
100100
expect:
101-
applyTemplate('<f:display bean="personInstance" property="dateOfBirth"/>', [personInstance: personInstance]) ==~ /1987-04-19 00:00:00 [A-Z]{3,4}/
101+
applyTemplate('<f:display bean="personInstance" property="dateOfBirth"/>', [personInstance: personInstance]) == applyTemplate('<g:formatDate date="${personInstance.dateOfBirth}" type="DATETIME"/>', [personInstance: personInstance])
102102
}
103103

104104
void 'displays using template if one is present'() {

0 commit comments

Comments
 (0)