Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ActionPropertyAccessor on Spring Framework 6.2 #1819

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2004-2012 the original author or authors.
* Copyright 2004-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -48,12 +48,13 @@ class DispatchMethodInvoker {
*/
@SuppressWarnings("serial")
private Map<String, Method> methodCache = new AbstractCachingMapDecorator<String, Method>() {
@Override
public Method create(String key) {
String methodName = key;
try {
return new MethodKey(target.getClass(), methodName, parameterTypes).getMethod();
} catch (InvalidMethodKeyException e) {
throw new MethodLookupException("Unable to resolve dispatch method " + e.getMethodKey()
throw new MethodLookupException("Unable to resolve dispatch method '" + e.getMethodKey()
+ "'; make sure the method name is correct and such a method is defined on targetClass "
+ target.getClass().getName(), e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2004-2012 the original author or authors.
* Copyright 2004-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,45 +15,64 @@
*/
package org.springframework.webflow.expression.spel;

import java.lang.reflect.Method;

import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.util.ReflectionUtils;
import org.springframework.webflow.action.MultiAction;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.AnnotatedAction;
import org.springframework.webflow.execution.RequestContext;

/**
* <p>
* Spring EL Property Accessor that allows invocation of methods against a resolved Web Flow action, typically a
* {@link MultiAction} in expressions.
* </p>
*
*
* @see org.springframework.webflow.action.EvaluateAction
*
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 2.1
*/
public class ActionPropertyAccessor implements PropertyAccessor {

@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class[] { Action.class };
}

@Override
public boolean canRead(EvaluationContext context, Object target, String name) {
return true;
// Ensure the target is an Action.
if (!(target instanceof Action)) {
return false;
}
// Ensure the method adheres to the signature required by:
// Action: execute(RequestContext)
// or
// MultiAction: <method name>(RequestContext)
Method method = ReflectionUtils.findMethod(target.getClass(), name, RequestContext.class);
return (method != null);
}

@Override
public TypedValue read(EvaluationContext context, Object target, String name) {
AnnotatedAction annotated = new AnnotatedAction((Action) target);
annotated.setMethod(name);
return new TypedValue(annotated);
}

@Override
public boolean canWrite(EvaluationContext context, Object target, String name) {
return false;
}

@Override
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
throw new AccessException("The Action cannot be set with an expression.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright 2004-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.webflow.action;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.webflow.config.AbstractFlowConfiguration;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.executor.FlowExecutor;
import org.springframework.webflow.test.MockExternalContext;

/**
* Integration tests for {@link MultiAction}.
*
* @author Sam Brannen
* @since 3.0.1
* @see <a href="https://github.com/spring-projects/spring-webflow/issues/1802">gh-1802</a>
*/
@SpringJUnitConfig
@DirtiesContext
class MultiActionIntegrationTests {

private static final String WITH_REQUEST_CONTEXT = "withRequestContext";

private static final String WITHOUT_REQUEST_CONTEXT = "withoutRequestContext";


@Autowired
FlowExecutor flowExecutor;

@Autowired
CountingMultiAction multiAction;


@BeforeEach
void resetCounters() {
multiAction.counterWithRequestContext = 0;
multiAction.counterWithoutRequestContext = 0;
}

@Test
void spelExpressionsWithRequestContext() {
assertCounters(0, 0);
launchFlowExecution(WITH_REQUEST_CONTEXT);
assertCounters(2, 0);
}

@Test
void spelExpressionsWithoutRequestContext() {
assertCounters(0, 0);
launchFlowExecution(WITHOUT_REQUEST_CONTEXT);
assertCounters(0, 2);
}

private void launchFlowExecution(String flowId) {
flowExecutor.launchExecution(flowId, null, new MockExternalContext());
}

private void assertCounters(int counterWithRequestContext, int counterWithoutRequestContext) {
assertEquals(counterWithRequestContext, multiAction.counterWithRequestContext, "counterWithRequestContext");
assertEquals(counterWithoutRequestContext, multiAction.counterWithoutRequestContext, "counterWithoutRequestContext");
}



@Configuration
static class WebFlowConfig extends AbstractFlowConfiguration {

@Bean
CountingMultiAction countingMultiAction() {
return new CountingMultiAction();
}

@Bean
FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry()).build();
}

@Bean
FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder()
.setBasePath("classpath:/org/springframework/webflow/action")
.addFlowLocation("multi-action-with-request-context.xml", WITH_REQUEST_CONTEXT)
.addFlowLocation("multi-action-without-request-context.xml", WITHOUT_REQUEST_CONTEXT)
.build();
}
}

@Component("countingMultiAction")
static class CountingMultiAction extends MultiAction {

int counterWithRequestContext = 0;

int counterWithoutRequestContext = 0;

public Event incrementWithRequestContext(RequestContext context) {
counterWithRequestContext++;
return success();
}

public Event incrementWithoutRequestContext() {
counterWithoutRequestContext++;
return success();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow https://www.springframework.org/schema/webflow/spring-webflow.xsd">

<on-start>
<!-- Property Access: handled by org.springframework.webflow.expression.spel.ActionPropertyAccessor -->
<evaluate expression="countingMultiAction.incrementWithRequestContext" />

<!-- Method Invocation: handled by org.springframework.expression.spel.ast.MethodReference -->
<evaluate expression="countingMultiAction.incrementWithRequestContext(#requestContext)" />
</on-start>

<end-state id="end" />

</flow>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow https://www.springframework.org/schema/webflow/spring-webflow.xsd">

<on-start>
<!-- Property Access: handled by org.springframework.expression.spel.support.ReflectivePropertyAccessor -->
<evaluate expression="countingMultiAction.incrementWithoutRequestContext" />

<!-- Method Invocation: handled by org.springframework.expression.spel.ast.MethodReference -->
<evaluate expression="countingMultiAction.incrementWithoutRequestContext()" />
</on-start>

<end-state id="end" />

</flow>