Skip to content

Commit 480fe14

Browse files
committed
Revert "Revert "- Revert: fix for GRAILS-6749 "subflow state redirects to wrong url"""
This reverts commit ed9d0f8.
1 parent 3a97eb9 commit 480fe14

File tree

7 files changed

+99
-49
lines changed

7 files changed

+99
-49
lines changed

grails-plugin-gsp/src/main/groovy/org/codehaus/groovy/grails/plugins/web/taglib/ApplicationTagLib.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ import org.springframework.beans.factory.InitializingBean
2929
import org.springframework.beans.factory.annotation.Autowired
3030
import org.springframework.context.ApplicationContext
3131
import org.springframework.context.ApplicationContextAware
32+
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest
3233

33-
/**
34+
/**
3435
* The base application tag library for Grails many of which take inspiration from Rails helpers (thanks guys! :)
3536
* This tag library tends to get extended by others as tags within here can be re-used in said libraries
3637
*
@@ -348,6 +349,9 @@ class ApplicationTagLib implements ApplicationContextAware, InitializingBean, Gr
348349
if (request['flowExecutionKey']) {
349350
params."execution" = request['flowExecutionKey']
350351
urlAttrs.params = params
352+
if (attrs.controller == null && attrs.action == null && attrs.url == null && attrs.uri == null) {
353+
urlAttrs[LinkGenerator.ATTRIBUTE_ACTION] = GrailsWebRequest.lookup().actionName
354+
}
351355
}
352356
if (urlAttrs.event) {
353357
params."_eventId" = urlAttrs.remove('event')

grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/webflow/SubflowExecutionTests.groovy

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ import org.codehaus.groovy.grails.webflow.support.AbstractGrailsTagAwareFlowExec
77
* @since 1.2
88
*/
99
class SubflowExecutionTests extends AbstractGrailsTagAwareFlowExecutionTests {
10+
def subberFlow = {
11+
subber {
12+
on("proceed").to("subberEnd")
13+
}
14+
subberEnd()
15+
}
1016

1117
void testSubFlowExecution() {
18+
registerFlow('subflowExecutionTests/subber', subberFlow)
1219
startFlow()
1320
assertCurrentStateEquals "start"
1421

@@ -20,13 +27,6 @@ class SubflowExecutionTests extends AbstractGrailsTagAwareFlowExecutionTests {
2027
}
2128

2229
Closure getFlowClosure() {
23-
def subberFlow = {
24-
subber {
25-
on("proceed").to("subberEnd")
26-
}
27-
subberEnd()
28-
}
29-
3030
return {
3131
start {
3232
on("next").to "subber"

grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/webflow/SubflowExecutionWithExternalSubflowTests.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.codehaus.groovy.grails.webflow.support.AbstractGrailsTagAwareFlowExec
77
class SubflowExecutionWithExternalSubflowTests extends AbstractGrailsTagAwareFlowExecutionTests {
88

99
void testSubFlowExecution() {
10+
registerFlow('otherSubflow/subber', new OtherSubflowController().subberFlow)
1011
startFlow()
1112
assertCurrentStateEquals "start"
1213

grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/webflow/engine/builder/FlowBuilderSubFlowExecutionTests.groovy

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import org.codehaus.groovy.grails.webflow.support.AbstractGrailsTagAwareFlowExec
88
class FlowBuilderSubFlowExecutionTests extends AbstractGrailsTagAwareFlowExecutionTests{
99

1010
def searchMoreAction = { [moreResults:["one", "two", "three"]] }
11-
12-
Closure getFlowClosure() {
13-
def searchService = [executeSearch:{["foo", "bar"]}]
14-
def params = [q:"foo"]
1511
def extendedSearchFlow = {
1612
startExtendedSearch {
1713
on("findMore").to "searchMore"
@@ -30,6 +26,10 @@ class FlowBuilderSubFlowExecutionTests extends AbstractGrailsTagAwareFlowExecuti
3026
noResults()
3127
}
3228

29+
Closure getFlowClosure() {
30+
def searchService = [executeSearch:{["foo", "bar"]}]
31+
def params = [q:"foo"]
32+
3333
return {
3434
displaySearchForm {
3535
on("submit").to "executeSearch"
@@ -68,6 +68,7 @@ class FlowBuilderSubFlowExecutionTests extends AbstractGrailsTagAwareFlowExecuti
6868
}
6969

7070
void testSubFlowDefinition() {
71+
registerFlow('flowBuilderSubFlowExecutionTests/extendedSearch', extendedSearchFlow)
7172
grails.util.GrailsWebUtil.bindMockWebRequest()
7273
def theFlow = getFlowDefinition()
7374

@@ -85,6 +86,7 @@ class FlowBuilderSubFlowExecutionTests extends AbstractGrailsTagAwareFlowExecuti
8586
}
8687

8788
void testSubFlowExecution() {
89+
registerFlow('flowBuilderSubFlowExecutionTests/extendedSearch', extendedSearchFlow)
8890
grails.util.GrailsWebUtil.bindMockWebRequest()
8991
startFlow()
9092
assertCurrentStateEquals "displaySearchForm"
@@ -103,6 +105,7 @@ class FlowBuilderSubFlowExecutionTests extends AbstractGrailsTagAwareFlowExecuti
103105

104106
void testSubFlowExecution2() {
105107
searchMoreAction = { error() }
108+
registerFlow('flowBuilderSubFlowExecutionTests/extendedSearch', extendedSearchFlow)
106109
startFlow()
107110
assertCurrentStateEquals "displaySearchForm"
108111

grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/webflow/support/AbstractGrailsTagAwareFlowExecutionTests.groovy

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ abstract class AbstractGrailsTagAwareFlowExecutionTests extends AbstractFlowExec
4848
ServletContext servletContext
4949
GrailsWebRequest webRequest
5050
FlowBuilderServices flowBuilderServices
51-
FlowDefinitionLocator definitionLocator = new FlowDefinitionRegistryImpl()
52-
51+
FlowDefinitionRegistry flowDefinitionRegistry = new FlowDefinitionRegistryImpl()
5352
MockHttpServletRequest request
5453
MockHttpServletResponse response
5554
def ctx
@@ -156,11 +155,17 @@ abstract class AbstractGrailsTagAwareFlowExecutionTests extends AbstractFlowExec
156155
return context
157156
}
158157

158+
FlowDefinition registerFlow(String flowId, Closure flowClosure) {
159+
FlowBuilder builder = new FlowBuilder(flowId, flowClosure, flowBuilderServices, getFlowDefinitionRegistry())
160+
builder.viewPath = "/"
161+
builder.applicationContext = appCtx
162+
FlowAssembler assembler = new FlowAssembler(builder, builder.getFlowBuilderContext())
163+
getFlowDefinitionRegistry().registerFlowDefinition(new DefaultFlowHolder(assembler))
164+
return getFlowDefinitionRegistry().getFlowDefinition(flowId)
165+
}
159166

160167
FlowDefinition getFlowDefinition() {
161-
FlowBuilder builder = new FlowBuilder(getFlowId(), getFlowBuilderServices(), getDefinitionLocator())
162-
builder.applicationContext = appCtx
163-
builder.flow(getFlowClosure())
168+
return registerFlow(getFlowId(), getFlowClosure())
164169
}
165170

166171
protected void onInit() {}

grails-webflow/src/main/groovy/grails/test/WebFlowTestCase.groovy

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ abstract class WebFlowTestCase extends AbstractFlowExecutionTests {
4848
protected MockHttpServletRequest mockRequest
4949
protected MockHttpServletResponse mockResponse
5050
protected MockServletContext mockServletContext
51+
protected FlowDefinitionRegistry flowDefinitionRegistry
5152
protected ApplicationContext applicationContext
53+
protected FlowBuilderServices flowBuilderServices
54+
protected MvcViewFactoryCreator viewCreator
55+
5256
/**
5357
* Subclasses should return the flow closure that within the controller. For example:
5458
* <code>return new TestController().myFlow</code>.
@@ -77,7 +81,18 @@ abstract class WebFlowTestCase extends AbstractFlowExecutionTests {
7781
mockServletContext = new MockServletContext()
7882
RequestContextHolder.setRequestAttributes(new GrailsWebRequest(mockRequest,mockResponse,mockServletContext,applicationContext))
7983
}
80-
84+
if (!applicationContext) applicationContext = new GrailsWebApplicationContext()
85+
flowDefinitionRegistry = new FlowDefinitionRegistryImpl()
86+
flowBuilderServices = new FlowBuilderServices()
87+
viewCreator = new MvcViewFactoryCreator()
88+
viewCreator.applicationContext = applicationContext
89+
def viewResolvers = applicationContext?.getBeansOfType(ViewResolver)
90+
if (viewResolvers) {
91+
viewCreator.viewResolvers = viewResolvers.values().toList()
92+
}
93+
flowBuilderServices.viewFactoryCreator = viewCreator
94+
flowBuilderServices.conversionService = new DefaultConversionService()
95+
flowBuilderServices.expressionParser = DefaultExpressionParserFactory.getExpressionParser()
8196
}
8297

8398
protected void tearDown() {
@@ -88,28 +103,24 @@ abstract class WebFlowTestCase extends AbstractFlowExecutionTests {
88103
RequestContextHolder.setRequestAttributes(null)
89104
}
90105

106+
FlowDefinition registerFlow(String flowId, Closure flowClosure) {
107+
FlowBuilder builder = new FlowBuilder(flowId, flowClosure, flowBuilderServices, flowDefinitionRegistry)
108+
builder.viewPath = "/"
109+
builder.applicationContext = applicationContext
110+
FlowAssembler assembler = new FlowAssembler(builder, builder.getFlowBuilderContext())
111+
flowDefinitionRegistry.registerFlowDefinition(new DefaultFlowHolder(assembler))
112+
return flowDefinitionRegistry.getFlowDefinition(flowId)
113+
}
91114

92115
FlowDefinition getFlowDefinition() {
93-
def flowBuilderServices = new FlowBuilderServices()
94-
95-
MvcViewFactoryCreator viewCreator = new MvcViewFactoryCreator()
96-
viewCreator.applicationContext = applicationContext
97-
def viewResolvers = applicationContext?.getBeansOfType(ViewResolver)
98-
if (viewResolvers) {
99-
viewCreator.viewResolvers = viewResolvers.values().toList()
100-
}
101-
flowBuilderServices.viewFactoryCreator = viewCreator
102-
flowBuilderServices.conversionService = new DefaultConversionService()
103-
flowBuilderServices.expressionParser = DefaultExpressionParserFactory.getExpressionParser()
104-
105-
FlowBuilder builder = new FlowBuilder(getFlowId(), flowBuilderServices, new FlowDefinitionRegistryImpl())
106116
def flow = getFlow()
107117

108118
if (flow instanceof Closure) {
109119
// delegate will be controller
110120
GrailsWebRequest.lookup()?.request?.setAttribute(GrailsApplicationAttributes.CONTROLLER, flow.delegate)
111-
builder.flow(flow)
112121
}
122+
123+
return registerFlow(getFlowId(), flow)
113124
}
114125

115126
/**

grails-webflow/src/main/groovy/org/codehaus/groovy/grails/webflow/engine/builder/FlowBuilder.groovy

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,13 @@ class FlowBuilder extends AbstractFlowBuilder implements GroovyObject, Applicati
9090
String viewPath = "/"
9191

9292
protected FlowBuilderServices flowBuilderServices
93+
protected FlowDefinitionLocator definitionLocator
9394

9495
FlowBuilder(String flowId, FlowBuilderServices flowBuilderServices, FlowDefinitionLocator definitionLocator) {
9596
this.flowId = flowId
9697
Assert.notNull flowBuilderServices, "Argument [flowBuilderServices] is required!"
9798
this.flowBuilderServices = flowBuilderServices
99+
this.definitionLocator = definitionLocator
98100
this.metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(FlowBuilder.class)
99101

100102
def context = new FlowBuilderContextImpl(flowId,null, definitionLocator, flowBuilderServices)
@@ -180,19 +182,8 @@ class FlowBuilder extends AbstractFlowBuilder implements GroovyObject, Applicati
180182
// add action state
181183
state = createActionState(name, action, trans, flowFactory, flowInfo.entryAction, flowInfo.exitAction)
182184
}
183-
else if (flowInfo.subflow) {
184-
def i = flowId.indexOf('/')
185-
def subflowClass = flowInfo.subflow.getThisObject().getClass()
186-
def controllerName = GrailsNameUtils.getLogicalPropertyName(subflowClass.name, "Controller")
187-
def subflowId = "${controllerName}/$name"
188-
FlowBuilder subFlowBuilder = new FlowBuilder(subflowId, flowBuilderServices,
189-
getContext().getFlowDefinitionLocator())
190-
191-
subFlowBuilder.viewPath = this.viewPath
192-
193-
Flow subflow = subFlowBuilder.flow(flowInfo.subflow)
194-
195-
state = createSubFlow(flowInfo, name, subflow, trans, flowFactory)
185+
else if (flowInfo.subflow || flowInfo.subflowAction) {
186+
state = createSubFlow(flowInfo, flowFactory, name)
196187
}
197188
else {
198189
String view = createViewPath(flowInfo, name)
@@ -296,8 +287,31 @@ class FlowBuilder extends AbstractFlowBuilder implements GroovyObject, Applicati
296287

297288
}
298289

299-
private State createSubFlow(flowInfo, String stateId, Flow subflow, Transition[] trans, FlowArtifactFactory flowFactory) {
300-
return flowFactory.createSubflowState(stateId, getFlow(), null, new StaticExpression(subflow),new GrailsSubflowAttributeMapper(flowInfo.subflowInput),trans, null, null, null)
290+
private State createSubFlow(flowInfo, FlowArtifactFactory flowFactory, stateId) {
291+
def subflowId
292+
if (flowInfo.subflow) {
293+
//backwards compatibility: only subflow closure is supplied and the containing state must have the same name as the called subflow
294+
def controllerClass = flowInfo.subflow.getThisObject().getClass()
295+
def controllerName = GrailsNameUtils.getLogicalPropertyName(controllerClass.name, "Controller")
296+
subflowId = "${controllerName}/$stateId"
297+
}
298+
else {
299+
//preferred way: subflow action and optional controller name are supplied
300+
def controllerName
301+
if (flowInfo.subflowController) {
302+
controllerName = flowInfo.subflowController
303+
}
304+
else {
305+
//assume that the subflow is defined in the same controller as the calling flow
306+
Class controllerClass = flowClosure.getThisObject().getClass()
307+
controllerName = GrailsNameUtils.getLogicalPropertyName(controllerClass.name, "Controller")
308+
}
309+
subflowId = "${controllerName}/$flowInfo.subflowAction"
310+
}
311+
Flow subflow = definitionLocator.getFlowDefinition(subflowId)
312+
313+
return flowFactory.createSubflowState(stateId, getFlow(), null, new StaticExpression(subflow),
314+
new GrailsSubflowAttributeMapper(flowInfo.subflowInput), flowInfo.transitions, null, null, null)
301315
}
302316

303317
private State createActionState(String stateId, Closure action, Transition[] transitions,
@@ -395,6 +409,8 @@ class FlowInfoCapturer {
395409
private Closure entryAction
396410
private Closure exitAction
397411
private Closure subflow
412+
private String subflowController
413+
private String subflowAction
398414
private Map subflowInput = [:]
399415
private String viewName
400416
private applicationContext
@@ -417,6 +433,8 @@ class FlowInfoCapturer {
417433
Closure getEntryAction() { this.entryAction}
418434
Closure getExitAction() { this.exitAction}
419435
Closure getSubflow() { this.subflow }
436+
String getSubflowController() { this.subflowController }
437+
String getSubflowAction() { this.subflowAction }
420438
Map getSubflowInput() { this.subflowInput }
421439
Mapper getOutputMapper() { this.outputMapper }
422440

@@ -459,8 +477,16 @@ class FlowInfoCapturer {
459477
this.subflow = callable
460478
}
461479

462-
void subflow(Map args, Closure callable) {
463-
this.subflow = callable
480+
void subflow(Map args, Closure callable = null) {
481+
if (callable != null) {
482+
this.subflow = callable
483+
} else {
484+
if (!args.action) {
485+
throw new FlowDefinitionException("subflow action is mandatory")
486+
}
487+
this.subflowController = args.controller
488+
this.subflowAction = args.action
489+
}
464490
args.input?.each {key, value ->
465491
if (value instanceof Closure) {
466492
this.subflowInput[key] = new ClosureExpression(value)

0 commit comments

Comments
 (0)