Skip to content

Commit e98b4fe

Browse files
committed
Merge branch '3.2.x' of github.com:grails/grails-core into 3.2.x
2 parents 5a1ae93 + e958ac5 commit e98b4fe

File tree

9 files changed

+564
-4
lines changed

9 files changed

+564
-4
lines changed

grails-plugin-databinding/src/main/groovy/org/grails/plugins/databinding/DataBindingGrailsPlugin.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import org.grails.web.databinding.bindingsource.DataBindingSourceRegistry
2323
import org.grails.web.databinding.bindingsource.DefaultDataBindingSourceRegistry
2424
import org.grails.web.databinding.bindingsource.HalJsonDataBindingSourceCreator
2525
import org.grails.web.databinding.bindingsource.HalXmlDataBindingSourceCreator
26+
import org.grails.web.databinding.bindingsource.JsonApiDataBindingSourceCreator
2627
import org.grails.web.databinding.bindingsource.JsonDataBindingSourceCreator
2728
import org.grails.web.databinding.bindingsource.XmlDataBindingSourceCreator
2829
import org.grails.databinding.converters.CurrencyValueConverter
@@ -96,6 +97,7 @@ class DataBindingGrailsPlugin extends Plugin {
9697
jsonDataBindingSourceCreator(JsonDataBindingSourceCreator)
9798
halJsonDataBindingSourceCreator(HalJsonDataBindingSourceCreator)
9899
halXmlDataBindingSourceCreator(HalXmlDataBindingSourceCreator)
100+
jsonApiDataBindingSourceCreator(JsonApiDataBindingSourceCreator)
99101

100102
defaultCurrencyConverter CurrencyValueConverter
101103
}}

grails-plugin-url-mappings/src/main/groovy/org/grails/plugins/web/mapping/UrlMappingsGrailsPlugin.groovy

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ import grails.web.CamelCaseUrlConverter
2323
import grails.web.HyphenatedUrlConverter
2424
import groovy.transform.CompileDynamic
2525
import groovy.transform.CompileStatic
26-
import grails.core.GrailsApplication
2726
import org.grails.core.artefact.UrlMappingsArtefactHandler
28-
import grails.core.support.GrailsApplicationAware
27+
import grails.web.mapping.cors.GrailsCorsConfiguration
2928
import org.grails.spring.beans.factory.HotSwappableTargetSourceFactoryBean
3029
import org.grails.web.mapping.CachingLinkGenerator
3130
import org.grails.web.mapping.DefaultLinkGenerator
@@ -39,7 +38,6 @@ import org.grails.web.mapping.servlet.UrlMappingsErrorPageCustomizer
3938
import org.springframework.aop.framework.ProxyFactoryBean
4039
import org.springframework.aop.target.HotSwappableTargetSource
4140
import org.springframework.context.ApplicationContext
42-
import org.springframework.web.context.WebApplicationContext
4341

4442
/**
4543
* Handles the configuration of URL mappings.
@@ -69,7 +67,11 @@ class UrlMappingsGrailsPlugin extends Plugin {
6967

7068
"${grails.web.UrlConverter.BEAN_NAME}"('hyphenated' == urlConverterType ? HyphenatedUrlConverter : CamelCaseUrlConverter)
7169

72-
urlMappingsHandlerMapping(UrlMappingsHandlerMapping, ref("grailsUrlMappingsHolder"))
70+
grailsCorsConfiguration(GrailsCorsConfiguration)
71+
72+
urlMappingsHandlerMapping(UrlMappingsHandlerMapping, ref("grailsUrlMappingsHolder")) {
73+
grailsCorsConfiguration = ref("grailsCorsConfiguration")
74+
}
7375
urlMappingsInfoHandlerAdapter(UrlMappingsInfoHandlerAdapter)
7476
urlMappingsErrorPageCustomizer(UrlMappingsErrorPageCustomizer)
7577
grailsLinkGenerator(cacheUrls ? CachingLinkGenerator : DefaultLinkGenerator, serverURL)

grails-web-common/src/main/groovy/grails/web/mime/MimeType.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class MimeType {
4444
public static final MimeType HAL_JSON = new MimeType('application/hal+json', "json")
4545
public static final MimeType HAL_XML = new MimeType('application/hal+xml', "xml")
4646
public static final MimeType ATOM_XML = new MimeType('application/atom+xml', "xml")
47+
public static final MimeType JSON_API = new MimeType('application/vnd.api+json', "json")
4748

4849
private static DEFAULTS = createDefaults()
4950
public static final String QUALITY_RATING = "1.0"
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2013 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+
* http://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.grails.web.databinding.bindingsource
17+
18+
import grails.web.mime.MimeType
19+
import groovy.transform.CompileStatic
20+
21+
/**
22+
* Creates DataBindingSource objects from JSON API in the request body
23+
*
24+
* @since 3.2.1
25+
*
26+
* @author James Kleeh
27+
*
28+
* @see grails.databinding.DataBindingSource
29+
* @see org.grails.databinding.bindingsource.DataBindingSourceCreator
30+
*/
31+
@CompileStatic
32+
class JsonApiDataBindingSourceCreator extends JsonDataBindingSourceCreator {
33+
34+
protected static final String DATA = "data"
35+
protected static final String RELATIONSHIPS = "relationships"
36+
protected static final String ID = "id"
37+
protected static final String ATTRIBUTES = "attributes"
38+
39+
@Override
40+
MimeType[] getMimeTypes() {
41+
[MimeType.JSON_API] as MimeType[]
42+
}
43+
44+
@Override
45+
protected Map createJsonMap(Object jsonElement) {
46+
if(jsonElement instanceof Map) {
47+
def jsonMap = (Map) jsonElement
48+
if(jsonMap.containsKey(DATA)) {
49+
jsonMap = new LinkedHashMap(jsonMap)
50+
def data = jsonMap.get(DATA)
51+
if (data instanceof Map) {
52+
if (data.containsKey(ID)) {
53+
jsonMap.put(ID, data.get(ID))
54+
}
55+
if (data.containsKey(ATTRIBUTES)) {
56+
jsonMap.putAll((Map)data.get(ATTRIBUTES))
57+
}
58+
if (data.containsKey(RELATIONSHIPS)) {
59+
Map relationships = (Map)data.get(RELATIONSHIPS)
60+
relationships.each { key, val ->
61+
if (val instanceof Map && ((Map)val).containsKey(DATA)) {
62+
def rData = ((Map)val).get(DATA)
63+
if (rData instanceof Map) {
64+
jsonMap.put(key, rData.get(ID))
65+
} else if (rData instanceof List) {
66+
jsonMap.put(key, rData.collect { d -> d[ID] })
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
return jsonMap
74+
}
75+
else {
76+
return super.createJsonMap(jsonElement)
77+
}
78+
}
79+
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package org.grails.web.databinding.bindingsource.json.api
2+
3+
import grails.databinding.DataBindingSource
4+
import org.grails.web.databinding.bindingsource.JsonApiDataBindingSourceCreator
5+
import spock.lang.Shared
6+
import spock.lang.Specification
7+
8+
/**
9+
* Created by jameskleeh on 9/29/16.
10+
*/
11+
class JsonApiDataBindingSourceCreatorSpec extends Specification {
12+
13+
@Shared
14+
JsonApiDataBindingSourceCreator creator = new JsonApiDataBindingSourceCreator()
15+
16+
void "test create single relationship"() {
17+
given:
18+
String json = '''{
19+
"data": {
20+
"type": "photos",
21+
"attributes": {
22+
"title": "Ember Hamster",
23+
"src": "http://example.com/images/productivity.png"
24+
},
25+
"relationships": {
26+
"photographer": {
27+
"data": {
28+
"type": "people",
29+
"id": "9"
30+
}
31+
}
32+
}
33+
}
34+
}'''
35+
36+
when:
37+
def inputStream = new ByteArrayInputStream(json.getBytes("UTF-8"))
38+
DataBindingSource source = creator.createDataBindingSource(null, null, inputStream)
39+
40+
then:
41+
source.containsProperty("data")
42+
source.getPropertyValue("title") == "Ember Hamster"
43+
source.getPropertyValue("src") == "http://example.com/images/productivity.png"
44+
source.getPropertyValue("photographer") == "9"
45+
46+
}
47+
48+
void "test create list of relationships"() {
49+
given:
50+
String json = '''{
51+
"data": {
52+
"type": "photos",
53+
"attributes": {
54+
"title": "Ember Hamster",
55+
"src": "http://example.com/images/productivity.png"
56+
},
57+
"relationships": {
58+
"photographer": {
59+
"data": [
60+
{
61+
"type": "people",
62+
"id": "9"
63+
},
64+
{
65+
"type": "people",
66+
"id": "10"
67+
}
68+
]
69+
}
70+
}
71+
}
72+
}'''
73+
74+
when:
75+
def inputStream = new ByteArrayInputStream(json.getBytes("UTF-8"))
76+
DataBindingSource source = creator.createDataBindingSource(null, null, inputStream)
77+
78+
then:
79+
source.containsProperty("data")
80+
source.getPropertyValue("title") == "Ember Hamster"
81+
source.getPropertyValue("src") == "http://example.com/images/productivity.png"
82+
source.getPropertyValue("photographer") == ["9", "10"]
83+
84+
}
85+
86+
void "test create no relationships - with key"() {
87+
given:
88+
String json = '''{
89+
"data": {
90+
"type": "photos",
91+
"attributes": {
92+
"title": "Ember Hamster",
93+
"src": "http://example.com/images/productivity.png"
94+
},
95+
"relationships": {
96+
}
97+
}
98+
}'''
99+
100+
when:
101+
def inputStream = new ByteArrayInputStream(json.getBytes("UTF-8"))
102+
DataBindingSource source = creator.createDataBindingSource(null, null, inputStream)
103+
104+
then:
105+
source.containsProperty("data")
106+
source.getPropertyValue("title") == "Ember Hamster"
107+
source.getPropertyValue("src") == "http://example.com/images/productivity.png"
108+
}
109+
110+
void "test create no relationships - without key"() {
111+
given:
112+
String json = '''{
113+
"data": {
114+
"type": "photos",
115+
"attributes": {
116+
"title": "Ember Hamster",
117+
"src": "http://example.com/images/productivity.png"
118+
}
119+
}
120+
}'''
121+
122+
when:
123+
def inputStream = new ByteArrayInputStream(json.getBytes("UTF-8"))
124+
DataBindingSource source = creator.createDataBindingSource(null, null, inputStream)
125+
126+
then:
127+
source.containsProperty("data")
128+
source.getPropertyValue("title") == "Ember Hamster"
129+
source.getPropertyValue("src") == "http://example.com/images/productivity.png"
130+
}
131+
132+
void "test create no attributes - with key"() {
133+
given:
134+
String json = '''{
135+
"data": {
136+
"type": "photos",
137+
"attributes": {
138+
},
139+
"relationships": {
140+
}
141+
}
142+
}'''
143+
144+
when:
145+
def inputStream = new ByteArrayInputStream(json.getBytes("UTF-8"))
146+
DataBindingSource source = creator.createDataBindingSource(null, null, inputStream)
147+
148+
then:
149+
source.containsProperty("data")
150+
}
151+
152+
void "test create no attributes - without key"() {
153+
given:
154+
String json = '''{
155+
"data": {
156+
"type": "photos"
157+
}
158+
}'''
159+
160+
when:
161+
def inputStream = new ByteArrayInputStream(json.getBytes("UTF-8"))
162+
DataBindingSource source = creator.createDataBindingSource(null, null, inputStream)
163+
164+
then:
165+
source.containsProperty("data")
166+
}
167+
168+
void "test create data id"() {
169+
given:
170+
String json = '''{
171+
"data": {
172+
"type": "photos",
173+
"id": "foo"
174+
}
175+
}'''
176+
177+
when:
178+
def inputStream = new ByteArrayInputStream(json.getBytes("UTF-8"))
179+
DataBindingSource source = creator.createDataBindingSource(null, null, inputStream)
180+
181+
then:
182+
source.containsProperty("data")
183+
source.getPropertyValue("id") == "foo"
184+
}
185+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2014 original 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+
* http://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 grails.web.mapping.cors
17+
18+
import grails.util.TypeConvertingMap
19+
import groovy.transform.CompileStatic
20+
import org.springframework.boot.context.properties.ConfigurationProperties
21+
import org.springframework.web.cors.CorsConfiguration
22+
23+
/**
24+
* A bean that stores config and converts it to the format expected by Spring
25+
*
26+
* @author James Kleeh
27+
* @since 3.2.1
28+
*/
29+
@CompileStatic
30+
@ConfigurationProperties(prefix = 'grails.cors')
31+
class GrailsCorsConfiguration {
32+
33+
Boolean enabled = false
34+
35+
@Delegate
36+
GrailsDefaultCorsMapping grailsCorsMapping = new GrailsDefaultCorsMapping()
37+
38+
Map<String, TypeConvertingMap> mappings = [:]
39+
40+
Map<String, CorsConfiguration> getCorsConfigurations() {
41+
Map<String, CorsConfiguration> corsConfigurationMap = [:]
42+
43+
if (enabled) {
44+
if (mappings.size() > 0) {
45+
mappings.each { String key, TypeConvertingMap config ->
46+
CorsConfiguration corsConfiguration = grailsCorsMapping.toSpringConfig()
47+
if (config.containsKey('allowedOrigins')) {
48+
corsConfiguration.allowedOrigins = config.list('allowedOrigins')
49+
}
50+
if (config.containsKey('allowedMethods')) {
51+
corsConfiguration.allowedMethods = config.list('allowedMethods')
52+
}
53+
if (config.containsKey('allowedHeaders')) {
54+
corsConfiguration.allowedHeaders = config.list('allowedHeaders')
55+
}
56+
if (config.containsKey('exposedHeaders')) {
57+
corsConfiguration.exposedHeaders = config.list('exposedHeaders')
58+
}
59+
if (config.containsKey('maxAge')) {
60+
corsConfiguration.maxAge = config.long('maxAge')
61+
}
62+
if (config.containsKey('allowCredentials')) {
63+
corsConfiguration.allowCredentials = config.boolean('allowCredentials')
64+
}
65+
corsConfigurationMap[key] = corsConfiguration
66+
}
67+
} else {
68+
corsConfigurationMap["/**"] = grailsCorsMapping.toSpringConfig()
69+
}
70+
}
71+
72+
corsConfigurationMap
73+
}
74+
}

0 commit comments

Comments
 (0)