From 0ae6edba379891c45af4935fb056ef715485b827 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Thu, 12 Jul 2012 20:12:20 +0300 Subject: [PATCH] add basic pre/post processing aka poor man's AOP SHL-40 --- .../shell/ExecutionProcessor.java | 45 +++++++++++ .../shell/SimpleExecutionStrategy.java | 29 ++++++- .../shell/SimpleExecutionStrategyTest.java | 80 +++++++++++++++++++ 3 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/springframework/shell/ExecutionProcessor.java create mode 100644 src/test/java/org/springframework/shell/SimpleExecutionStrategyTest.java diff --git a/src/main/java/org/springframework/shell/ExecutionProcessor.java b/src/main/java/org/springframework/shell/ExecutionProcessor.java new file mode 100644 index 00000000..90c01af3 --- /dev/null +++ b/src/main/java/org/springframework/shell/ExecutionProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011-2012 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 + * + * http://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.shell; + +import org.springframework.roo.shell.CommandMarker; +import org.springframework.roo.shell.ParseResult; + +/** + * Extension interface allowing command provider to be called + * in a generic fashion just before, and right after, executing a command. + * + * @author Costin Leau + */ +public interface ExecutionProcessor extends CommandMarker { + + /** + * Method called before invoking the target command (described by {@link ParseResult}). + * Additionally, for advanced cases, the parse result itself effectively changing the invocation + * calling site. + * + * @param invocationContext target command context + * @return the invocation target + */ + ParseResult beforeInvocation(ParseResult invocationContext); + + /** + * Method called after invoking the target command (described by {@link ParseResult}). + * + * @param invocationContext target command context + */ + void afterInvocation(ParseResult invocationContext); +} diff --git a/src/main/java/org/springframework/shell/SimpleExecutionStrategy.java b/src/main/java/org/springframework/shell/SimpleExecutionStrategy.java index 11a2c3ac..3d20dd02 100644 --- a/src/main/java/org/springframework/shell/SimpleExecutionStrategy.java +++ b/src/main/java/org/springframework/shell/SimpleExecutionStrategy.java @@ -1,22 +1,47 @@ package org.springframework.shell; +import org.springframework.roo.shell.CommandMarker; import org.springframework.roo.shell.ExecutionStrategy; import org.springframework.roo.shell.ParseResult; import org.springframework.roo.support.util.Assert; import org.springframework.roo.support.util.ReflectionUtils; +/** + * Simple execution strategy for invoking a target method. + * Supports pre/post processing to allow {@link CommandMarker}s for aop-like behavior ( + * typically used for controlling stateful objects). + * + * @author Mark Pollack + * @author Costin Leau + */ public class SimpleExecutionStrategy implements ExecutionStrategy { private final Class mutex = SimpleExecutionStrategy.class; - + public Object execute(ParseResult parseResult) throws RuntimeException { Assert.notNull(parseResult, "Parse result required"); synchronized (mutex) { Assert.isTrue(isReadyForCommands(), "ProcessManagerHostedExecutionStrategy not yet ready for commands"); - return ReflectionUtils.invokeMethod(parseResult.getMethod(), parseResult.getInstance(), parseResult.getArguments()); + Object target = parseResult.getInstance(); + if (target instanceof ExecutionProcessor) { + ExecutionProcessor processor = ((ExecutionProcessor) target); + parseResult = processor.beforeInvocation(parseResult); + try { + return invoke(parseResult); + } finally { + processor.afterInvocation(parseResult); + } + } + else { + return invoke(parseResult); + } } } + private Object invoke(ParseResult parseResult) { + return ReflectionUtils.invokeMethod(parseResult.getMethod(), parseResult.getInstance(), parseResult.getArguments()); + } + public boolean isReadyForCommands() { return true; } diff --git a/src/test/java/org/springframework/shell/SimpleExecutionStrategyTest.java b/src/test/java/org/springframework/shell/SimpleExecutionStrategyTest.java new file mode 100644 index 00000000..8bedc23d --- /dev/null +++ b/src/test/java/org/springframework/shell/SimpleExecutionStrategyTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2011-2012 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 + * + * http://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.shell; + +import java.lang.reflect.Method; + +import org.junit.Test; +import org.springframework.roo.shell.ParseResult; +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.*; + +/** + * @author Costin Leau + */ +public class SimpleExecutionStrategyTest { + + public static class Target implements ExecutionProcessor { + Object before = null; + Object after = null; + ParseResult beforeReturn = null; + + public String one() { + return "one"; + } + + public String two() { + return "two"; + } + + public ParseResult beforeInvocation(ParseResult invocationContext) { + before = invocationContext; + return (beforeReturn == null ? invocationContext : beforeReturn); + } + + public void afterInvocation(ParseResult invocationContext) { + after = invocationContext; + } + } + + private SimpleExecutionStrategy execution = new SimpleExecutionStrategy(); + + @Test + public void testSimpleCommandProcessor() throws Exception { + Target target = new Target(); + Method one = ReflectionUtils.findMethod(target.getClass(), "one"); + ParseResult result = new ParseResult(one, target, null); + + assertEquals("one", execution.execute(result)); + assertSame(result, target.before); + assertSame(result, target.after); + } + + @Test + public void testRedirectCommandProcessor() throws Exception { + Target target = new Target(); + Method one = ReflectionUtils.findMethod(target.getClass(), "one"); + Method two = ReflectionUtils.findMethod(target.getClass(), "two"); + ParseResult given = new ParseResult(one, target, null); + ParseResult redirect = new ParseResult(two, target, null); + target.beforeReturn = redirect; + + assertEquals("two", execution.execute(given)); + assertSame(given, target.before); + assertSame(redirect, target.after); + } +} \ No newline at end of file