Skip to content

Commit e49b141

Browse files
committed
Merge grace-plugin-async into the framework
2 parents 8853c8c + e0ae72f commit e49b141

20 files changed

Lines changed: 1299 additions & 0 deletions

grace-plugin-async/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
## grails-plugin-async
2+

grace-plugin-async/app/.gitkeep

Whitespace-only changes.

grace-plugin-async/build.gradle

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
dependencies {
2+
compileOnly "jakarta.servlet:jakarta.servlet-api"
3+
implementation "org.graceframework:grace-plugin-controllers"
4+
api project(':grace-async-core')
5+
6+
api("org.springframework.boot:spring-boot-autoconfigure")
7+
annotationProcessor("org.springframework.boot:spring-boot-autoconfigure-processor")
8+
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
9+
10+
testImplementation "jakarta.servlet:jakarta.servlet-api"
11+
testImplementation "org.graceframework:grace-test"
12+
testImplementation "org.springframework:spring-test"
13+
}
14+
15+
jar {
16+
enabled = true
17+
archiveClassifier.set('plugin')
18+
includeEmptyDirs = false
19+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2013 SpringSource
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.async.services
17+
18+
import groovy.transform.CompileStatic
19+
import grails.persistence.support.PersistenceContextInterceptorExecutor
20+
import grails.async.decorator.PromiseDecorator
21+
22+
/**
23+
* A {@link PromiseDecorator} that wraps a promise execution in a persistence context (example Hibernate session)
24+
*
25+
* @author Graeme Rocher
26+
* @since 2.3
27+
*/
28+
@CompileStatic
29+
class PersistenceContextPromiseDecorator implements PromiseDecorator{
30+
PersistenceContextInterceptorExecutor persistenceContextInterceptorExecutor
31+
32+
PersistenceContextPromiseDecorator(PersistenceContextInterceptorExecutor persistenceContextInterceptorExecutor) {
33+
this.persistenceContextInterceptorExecutor = persistenceContextInterceptorExecutor
34+
}
35+
36+
@Override
37+
def <D> Closure<D> decorate(Closure<D> original) {
38+
if (persistenceContextInterceptorExecutor != null) {
39+
return { args ->
40+
try {
41+
persistenceContextInterceptorExecutor.initPersistenceContext()
42+
return original.call(args)
43+
} finally {
44+
persistenceContextInterceptorExecutor.destroyPersistenceContext()
45+
}
46+
}
47+
}
48+
return original
49+
}
50+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2013 SpringSource
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.async.services
17+
18+
import groovy.transform.CompileStatic
19+
import org.springframework.beans.BeanWrapper
20+
import org.springframework.beans.MutablePropertyValues
21+
import org.springframework.beans.PropertyAccessorFactory
22+
23+
import grails.async.decorator.PromiseDecorator
24+
//import org.springframework.beans.annotation.AnnotationBeanUtils
25+
import org.springframework.transaction.PlatformTransactionManager
26+
import org.springframework.transaction.TransactionDefinition
27+
import org.springframework.transaction.annotation.Transactional
28+
import org.springframework.transaction.support.DefaultTransactionDefinition
29+
import org.springframework.transaction.support.TransactionCallback
30+
import org.springframework.transaction.support.TransactionTemplate
31+
32+
/**
33+
* A {@link PromiseDecorator} that wraps a {@link grails.async.Promise} in a transaction
34+
*
35+
* @author Graeme Rocher
36+
* @since 2.3
37+
*/
38+
@CompileStatic
39+
class TransactionalPromiseDecorator implements PromiseDecorator, TransactionDefinition {
40+
41+
PlatformTransactionManager transactionManager
42+
@Delegate DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition()
43+
44+
TransactionalPromiseDecorator(PlatformTransactionManager transactionManager) {
45+
this.transactionManager = transactionManager
46+
}
47+
48+
TransactionalPromiseDecorator(PlatformTransactionManager transactionManager, DefaultTransactionDefinition transactionDefinition) {
49+
this.transactionManager = transactionManager
50+
this.transactionDefinition = transactionDefinition
51+
}
52+
53+
TransactionalPromiseDecorator(PlatformTransactionManager transactionManager, Transactional transactionDefinition) {
54+
this.transactionManager = transactionManager
55+
final definition = new DefaultTransactionDefinition()
56+
// AnnotationBeanUtils.copyPropertiesToBean(transactionDefinition, definition)
57+
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(definition)
58+
bw.setPropertyValues(new MutablePropertyValues(transactionDefinition.getProperties()))
59+
this.transactionDefinition = definition
60+
}
61+
62+
@Override
63+
def <D> Closure<D> decorate(Closure<D> original) {
64+
if (transactionManager != null) {
65+
return (Closure<D>){ args ->
66+
def transactionTemplate = transactionDefinition != null ? new TransactionTemplate(transactionManager, transactionDefinition) : new TransactionTemplate(transactionManager)
67+
transactionTemplate.execute({
68+
original.call(args)
69+
} as TransactionCallback)
70+
}
71+
}
72+
return original
73+
}
74+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package grails.async.web
2+
3+
import org.grails.plugins.web.async.GrailsAsyncContext
4+
import org.grails.web.servlet.mvc.GrailsWebRequest
5+
import org.grails.web.util.GrailsApplicationAttributes
6+
import org.springframework.web.context.request.RequestContextHolder
7+
import org.springframework.web.context.request.async.AsyncWebRequest
8+
import org.springframework.web.context.request.async.WebAsyncManager
9+
import org.springframework.web.context.request.async.WebAsyncUtils
10+
11+
import jakarta.servlet.AsyncContext
12+
import jakarta.servlet.http.HttpServletRequest
13+
14+
/**
15+
* Exposes a startAsync() method for access to the Servlet 3.x API
16+
*
17+
* @author Graeme Rocher
18+
* @since 3.3
19+
*/
20+
trait AsyncController {
21+
/**
22+
* Raw access to the Servlet 3.0 startAsync method
23+
*
24+
* @return a new {@link jakarta.servlet.AsyncContext}
25+
*/
26+
AsyncContext startAsync() {
27+
GrailsWebRequest webRequest = (GrailsWebRequest)RequestContextHolder.currentRequestAttributes()
28+
29+
HttpServletRequest request = webRequest.currentRequest
30+
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request)
31+
32+
AsyncWebRequest asyncWebRequest = new AsyncGrailsWebRequest(request, webRequest.currentResponse, webRequest.servletContext)
33+
asyncManager.setAsyncWebRequest(asyncWebRequest)
34+
35+
asyncWebRequest.startAsync()
36+
request.setAttribute(GrailsApplicationAttributes.ASYNC_STARTED, true)
37+
new GrailsAsyncContext(asyncWebRequest.asyncContext, webRequest)
38+
}
39+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package grails.async.web
2+
3+
import groovy.transform.CompileStatic
4+
import org.grails.web.util.GrailsApplicationAttributes
5+
import org.grails.web.servlet.mvc.GrailsWebRequest
6+
import org.springframework.context.ApplicationContext
7+
import org.springframework.util.Assert
8+
import org.springframework.web.context.request.async.AsyncWebRequest
9+
10+
import jakarta.servlet.AsyncContext
11+
import jakarta.servlet.AsyncEvent
12+
import jakarta.servlet.AsyncListener
13+
import jakarta.servlet.ServletContext
14+
import jakarta.servlet.http.HttpServletRequest
15+
import jakarta.servlet.http.HttpServletResponse
16+
import java.util.concurrent.atomic.AtomicBoolean
17+
import java.util.function.Consumer
18+
19+
/**
20+
* Implementation of Spring 4.0 {@link AsyncWebRequest} interface for Grails
21+
*
22+
* @author Graeme Rocher
23+
* @since 3.0
24+
*/
25+
@CompileStatic
26+
class AsyncGrailsWebRequest extends GrailsWebRequest implements AsyncWebRequest, AsyncListener{
27+
static final String WEB_REQUEST = "org.grails.ASYNC_WEB_REQUEST"
28+
29+
Long timeout
30+
AsyncContext asyncContext
31+
32+
private AtomicBoolean asyncCompleted = new AtomicBoolean(false)
33+
34+
List<Runnable> timeoutHandlers = []
35+
List<Runnable> completionHandlers = []
36+
List<Consumer<Throwable>> exceptionHandlers = []
37+
38+
AsyncGrailsWebRequest(HttpServletRequest request, HttpServletResponse response, GrailsApplicationAttributes attributes) {
39+
super(request, response, attributes)
40+
request.setAttribute(WEB_REQUEST, this)
41+
}
42+
43+
AsyncGrailsWebRequest(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
44+
super(request, response, servletContext)
45+
request.setAttribute(WEB_REQUEST, this)
46+
}
47+
48+
AsyncGrailsWebRequest(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, ApplicationContext applicationContext) {
49+
super(request, response, servletContext, applicationContext)
50+
request.setAttribute(WEB_REQUEST, this)
51+
}
52+
53+
/**
54+
* Looks up the GrailsWebRequest from the current request.
55+
* @param request The current request
56+
* @return The GrailsWebRequest
57+
*/
58+
static AsyncGrailsWebRequest lookup(HttpServletRequest request) {
59+
AsyncGrailsWebRequest webRequest = (AsyncGrailsWebRequest) request.getAttribute(WEB_REQUEST);
60+
return webRequest
61+
}
62+
63+
@Override
64+
void addTimeoutHandler(Runnable runnable) {
65+
timeoutHandlers << runnable
66+
}
67+
68+
@Override
69+
void addErrorHandler(Consumer<Throwable> exceptionHandler) {
70+
exceptionHandlers << exceptionHandler
71+
}
72+
73+
@Override
74+
void addCompletionHandler(Runnable runnable) {
75+
completionHandlers << runnable
76+
}
77+
78+
@Override
79+
void startAsync() {
80+
Assert.state(request.asyncSupported, "The current request does not support Async processing")
81+
if(!isAsyncStarted()) {
82+
asyncContext = request.startAsync(request, response)
83+
asyncContext.addListener(this)
84+
}
85+
}
86+
87+
@Override
88+
boolean isAsyncStarted() {
89+
asyncContext && request.asyncStarted
90+
}
91+
92+
@Override
93+
void dispatch() {
94+
Assert.notNull this.asyncContext, "Cannot dispatch without an AsyncContext"
95+
asyncContext.dispatch()
96+
}
97+
98+
@Override
99+
boolean isAsyncComplete() {
100+
return this.asyncCompleted.get()
101+
}
102+
103+
@Override
104+
void onComplete(AsyncEvent event) throws IOException {
105+
for(handler in completionHandlers) {
106+
handler.run()
107+
}
108+
asyncContext = null
109+
asyncCompleted.set true
110+
}
111+
112+
@Override
113+
void onTimeout(AsyncEvent event) throws IOException {
114+
for(handler in timeoutHandlers) {
115+
handler.run()
116+
}
117+
}
118+
119+
@Override
120+
void onError(AsyncEvent event) throws IOException {}
121+
122+
@Override
123+
void onStartAsync(AsyncEvent event) throws IOException {}
124+
}

0 commit comments

Comments
 (0)