diff --git a/org.springframework.aspects/.classpath b/org.springframework.aspects/.classpath
index 031c0a9a59d..0157912a92e 100644
--- a/org.springframework.aspects/.classpath
+++ b/org.springframework.aspects/.classpath
@@ -1,19 +1,20 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.springframework.aspects/ivy.xml b/org.springframework.aspects/ivy.xml
index 415b3dc3dd2..30108ea69c8 100644
--- a/org.springframework.aspects/ivy.xml
+++ b/org.springframework.aspects/ivy.xml
@@ -29,7 +29,8 @@
-
+
+
diff --git a/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/AbstractMethodMockingControl.aj b/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/AbstractMethodMockingControl.aj
new file mode 100644
index 00000000000..ffdaa6b047a
--- /dev/null
+++ b/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/AbstractMethodMockingControl.aj
@@ -0,0 +1,182 @@
+package org.springframework.mock.static_mock;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Abstract aspect to enable mocking of methods picked out by a pointcut.
+ * Sub-aspects must define the mockStaticsTestMethod() pointcut to
+ * indicate call stacks when mocking should be triggered, and the
+ * methodToMock() pointcut to pick out a method invocations to mock.
+ *
+ * @author Rod Johnson
+ * @author Ramnivas Laddad
+ *
+ */
+public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMethod()) {
+
+ protected abstract pointcut mockStaticsTestMethod();
+
+ protected abstract pointcut methodToMock();
+
+ private boolean recording = true;
+
+ static enum CallResponse { nothing, return_, throw_ };
+
+ // Represents a list of expected calls to static entity methods
+ // Public to allow inserted code to access: is this normal??
+ public class Expectations {
+
+ // Represents an expected call to a static entity method
+ private class Call {
+ private final String signature;
+ private final Object[] args;
+
+ private Object responseObject; // return value or throwable
+ private CallResponse responseType = CallResponse.nothing;
+
+ public Call(String name, Object[] args) {
+ this.signature = name;
+ this.args = args;
+ }
+
+ public boolean hasResponseSpecified() {
+ return responseType != CallResponse.nothing;
+ }
+
+ public void setReturnVal(Object retVal) {
+ this.responseObject = retVal;
+ responseType = CallResponse.return_;
+ }
+
+ public void setThrow(Throwable throwable) {
+ this.responseObject = throwable;
+ responseType = CallResponse.throw_;
+ }
+
+ public Object returnValue(String lastSig, Object[] args) {
+ checkSignature(lastSig, args);
+ return responseObject;
+ }
+
+ public Object throwException(String lastSig, Object[] args) {
+ checkSignature(lastSig, args);
+ throw (RuntimeException)responseObject;
+ }
+
+ private void checkSignature(String lastSig, Object[] args) {
+ if (!signature.equals(lastSig)) {
+ throw new IllegalArgumentException("Signature doesn't match");
+ }
+ if (!Arrays.equals(this.args, args)) {
+ throw new IllegalArgumentException("Arguments don't match");
+ }
+ }
+ }
+
+ private List calls = new LinkedList();
+
+ // Calls already verified
+ private int verified;
+
+ public void verify() {
+ if (verified != calls.size()) {
+ throw new IllegalStateException("Expected " + calls.size()
+ + " calls, received " + verified);
+ }
+ }
+
+ /**
+ * Validate the call and provide the expected return value
+ * @param lastSig
+ * @param args
+ * @return
+ */
+ public Object respond(String lastSig, Object[] args) {
+ Call call = nextCall();
+ CallResponse responseType = call.responseType;
+ if (responseType == CallResponse.return_) {
+ return call.returnValue(lastSig, args);
+ } else if(responseType == CallResponse.throw_) {
+ return (RuntimeException)call.throwException(lastSig, args);
+ } else if(responseType == CallResponse.nothing) {
+ // do nothing
+ }
+ throw new IllegalStateException("Behavior of " + call + " not specified");
+ }
+
+ private Call nextCall() {
+ if (verified > calls.size() - 1) {
+ throw new IllegalStateException("Expected " + calls.size()
+ + " calls, received " + verified);
+ }
+ return calls.get(verified++);
+ }
+
+ public void expectCall(String lastSig, Object lastArgs[]) {
+ Call call = new Call(lastSig, lastArgs);
+ calls.add(call);
+ }
+
+ public boolean hasCalls() {
+ return !calls.isEmpty();
+ }
+
+ public void expectReturn(Object retVal) {
+ Call call = calls.get(calls.size() - 1);
+ if (call.hasResponseSpecified()) {
+ throw new IllegalStateException("No static method invoked before setting return value");
+ }
+ call.setReturnVal(retVal);
+ }
+
+ public void expectThrow(Throwable throwable) {
+ Call call = calls.get(calls.size() - 1);
+ if (call.hasResponseSpecified()) {
+ throw new IllegalStateException("No static method invoked before setting throwable");
+ }
+ call.setThrow(throwable);
+ }
+ }
+
+ private Expectations expectations = new Expectations();
+
+ after() returning : mockStaticsTestMethod() {
+ if (recording && (expectations.hasCalls())) {
+ throw new IllegalStateException(
+ "Calls recorded, yet playback state never reached: Create expectations then call "
+ + this.getClass().getSimpleName() + ".playback()");
+ }
+ expectations.verify();
+ }
+
+ Object around() : methodToMock() {
+ if (recording) {
+ expectations.expectCall(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs());
+ // Return value doesn't matter
+ return null;
+ } else {
+ return expectations.respond(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs());
+ }
+ }
+
+ public void expectReturnInternal(Object retVal) {
+ if (!recording) {
+ throw new IllegalStateException("Not recording: Cannot set return value");
+ }
+ expectations.expectReturn(retVal);
+ }
+
+ public void expectThrowInternal(Throwable throwable) {
+ if (!recording) {
+ throw new IllegalStateException("Not recording: Cannot set throwable value");
+ }
+ expectations.expectThrow(throwable);
+ }
+
+ public void playbackInternal() {
+ recording = false;
+ }
+
+}
diff --git a/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/JUnitStaticEntityMockingControl.aj b/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/JUnitStaticEntityMockingControl.aj
new file mode 100644
index 00000000000..c732d7800be
--- /dev/null
+++ b/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/JUnitStaticEntityMockingControl.aj
@@ -0,0 +1,57 @@
+package org.springframework.mock.static_mock;
+
+import javax.persistence.Entity;
+import org.junit.Test;
+
+/**
+ * JUnit-specific aspect to use in test build to enable mocking static methods
+ * on Entity classes, as used by Roo for finders.
+ *
+ * Mocking will occur in JUnit tests where the Test class is annotated with the
+ * @MockStaticEntityMethods annotation, in the call stack of each
+ * JUnit @Test method.
+ *
+ * Also provides static methods to simplify the programming model for
+ * entering playback mode and setting expected return values.
+ *
+ * Usage:
+ * - Annotate a JUnit test class with @MockStaticEntityMethods.
+ *
- In each @Test method, JUnitMockControl will begin in recording mode.
+ * Invoke static methods on Entity classes, with each recording-mode invocation
+ * being followed by an invocation to the static expectReturn() or expectThrow()
+ * method on JUnitMockControl.
+ *
- Invoke the static JUnitMockControl.playback() method.
+ *
- Call the code you wish to test that uses the static methods. Verification will
+ * occur automatically.
+ *
+ *
+ * @see MockStaticEntityMethods
+ *
+ * @author Rod Johnson
+ * @author Ramnivas Laddad
+ *
+ */
+public aspect JUnitStaticEntityMockingControl extends AbstractMethodMockingControl {
+
+ /**
+ * Stop recording mock calls and enter playback state
+ */
+ public static void playback() {
+ JUnitStaticEntityMockingControl.aspectOf().playbackInternal();
+ }
+
+ public static void expectReturn(Object retVal) {
+ JUnitStaticEntityMockingControl.aspectOf().expectReturnInternal(retVal);
+ }
+
+ public static void expectThrow(Throwable throwable) {
+ JUnitStaticEntityMockingControl.aspectOf().expectThrowInternal(throwable);
+ }
+
+ // Only matches directly annotated @Test methods, to allow methods in
+ // @MockStatics classes to invoke each other without resetting the mocking environment
+ protected pointcut mockStaticsTestMethod() : execution(@Test public * (@MockStaticEntityMethods *).*(..));
+
+ protected pointcut methodToMock() : execution(public static * (@Entity *).*(..));
+
+}
diff --git a/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/MockStaticEntityMethods.java b/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/MockStaticEntityMethods.java
new file mode 100644
index 00000000000..b2efe668d2a
--- /dev/null
+++ b/org.springframework.aspects/src/main/java/org/springframework/mock/static_mock/MockStaticEntityMethods.java
@@ -0,0 +1,21 @@
+package org.springframework.mock.static_mock;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate a test class for whose @Test methods
+ * static methods on Entity classes should be mocked.
+ *
+ * @see AbstractMethodMockingControl
+ *
+ * @author Rod Johnson
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface MockStaticEntityMethods {
+
+}
diff --git a/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/JUnitStaticEntityMockingControlTest.java b/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/JUnitStaticEntityMockingControlTest.java
new file mode 100644
index 00000000000..34fa82b7fcf
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/JUnitStaticEntityMockingControlTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2009 SpringSource Inc.
+ *
+ * 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.mock.static_mock;
+
+import java.rmi.RemoteException;
+
+import javax.persistence.PersistenceException;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.springframework.remoting.RemoteAccessException;
+
+
+/**
+ * Test for static entity mocking framework.
+ * @author Rod Johnson
+ * @author Ramnivas Laddad
+ *
+ */
+@MockStaticEntityMethods
+@RunWith(JUnit4.class)
+public class JUnitStaticEntityMockingControlTest {
+
+ @Test
+ public void testNoArgIntReturn() {
+ int expectedCount = 13;
+ Person.countPeople();
+ JUnitStaticEntityMockingControl.expectReturn(expectedCount);
+ JUnitStaticEntityMockingControl.playback();
+ Assert.assertEquals(expectedCount, Person.countPeople());
+ }
+
+ @Test(expected=PersistenceException.class)
+ public void testNoArgThrows() {
+ Person.countPeople();
+ JUnitStaticEntityMockingControl.expectThrow(new PersistenceException());
+ JUnitStaticEntityMockingControl.playback();
+ Person.countPeople();
+ }
+
+ @Test
+ public void testArgMethodMatches() {
+ long id = 13;
+ Person found = new Person();
+ Person.findPerson(id);
+ JUnitStaticEntityMockingControl.expectReturn(found);
+ JUnitStaticEntityMockingControl.playback();
+ Assert.assertEquals(found, Person.findPerson(id));
+ }
+
+
+ @Test
+ public void testLongSeriesOfCalls() {
+ long id1 = 13;
+ long id2 = 24;
+ Person found1 = new Person();
+ Person.findPerson(id1);
+ JUnitStaticEntityMockingControl.expectReturn(found1);
+ Person found2 = new Person();
+ Person.findPerson(id2);
+ JUnitStaticEntityMockingControl.expectReturn(found2);
+ Person.findPerson(id1);
+ JUnitStaticEntityMockingControl.expectReturn(found1);
+ Person.countPeople();
+ JUnitStaticEntityMockingControl.expectReturn(0);
+ JUnitStaticEntityMockingControl.playback();
+
+ Assert.assertEquals(found1, Person.findPerson(id1));
+ Assert.assertEquals(found2, Person.findPerson(id2));
+ Assert.assertEquals(found1, Person.findPerson(id1));
+ Assert.assertEquals(0, Person.countPeople());
+ }
+
+ // Note delegation is used when tests are invalid and should fail, as otherwise
+ // the failure will occur on the verify() method in the aspect after
+ // this method returns, failing the test case
+ @Test
+ public void testArgMethodNoMatchExpectReturn() {
+ try {
+ new Delegate().testArgMethodNoMatchExpectReturn();
+ Assert.fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testArgMethodNoMatchExpectThrow() {
+ new Delegate().testArgMethodNoMatchExpectThrow();
+ }
+
+ private void called(Person found, long id) {
+ Assert.assertEquals(found, Person.findPerson(id));
+ }
+
+ @Test
+ public void testReentrant() {
+ long id = 13;
+ Person found = new Person();
+ Person.findPerson(id);
+ JUnitStaticEntityMockingControl.expectReturn(found);
+ JUnitStaticEntityMockingControl.playback();
+ called(found, id);
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testRejectUnexpectedCall() {
+ new Delegate().rejectUnexpectedCall();
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testFailTooFewCalls() {
+ new Delegate().failTooFewCalls();
+ }
+
+ @Test
+ public void testEmpty() {
+ // Test that verification check doesn't blow up if no replay() call happened
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testDoesntEverReplay() {
+ new Delegate().doesntEverReplay();
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testDoesntEverSetReturn() {
+ new Delegate().doesntEverSetReturn();
+ }
+}
+
+// Used because verification failures occur after method returns,
+// so we can't test for them in the test case itself
+@MockStaticEntityMethods
+class Delegate {
+
+ @Test
+ public void testArgMethodNoMatchExpectReturn() {
+ long id = 13;
+ Person found = new Person();
+ Person.findPerson(id);
+ JUnitStaticEntityMockingControl.expectReturn(found);
+ JUnitStaticEntityMockingControl.playback();
+ Assert.assertEquals(found, Person.findPerson(id + 1));
+ }
+
+ @Test
+ public void testArgMethodNoMatchExpectThrow() {
+ long id = 13;
+ Person found = new Person();
+ Person.findPerson(id);
+ JUnitStaticEntityMockingControl.expectThrow(new PersistenceException());
+ JUnitStaticEntityMockingControl.playback();
+ Assert.assertEquals(found, Person.findPerson(id + 1));
+ }
+
+ @Test
+ public void failTooFewCalls() {
+ long id = 13;
+ Person found = new Person();
+ Person.findPerson(id);
+ JUnitStaticEntityMockingControl.expectReturn(found);
+ Person.countPeople();
+ JUnitStaticEntityMockingControl.expectReturn(25);
+ JUnitStaticEntityMockingControl.playback();
+ Assert.assertEquals(found, Person.findPerson(id));
+ }
+
+ @Test
+ public void doesntEverReplay() {
+ Person.countPeople();
+ }
+
+ @Test
+ public void doesntEverSetReturn() {
+ Person.countPeople();
+ JUnitStaticEntityMockingControl.playback();
+ }
+
+ @Test
+ public void rejectUnexpectedCall() {
+ JUnitStaticEntityMockingControl.playback();
+ Person.countPeople();
+ }
+
+ @Test(expected=RemoteException.class)
+ public void testVerificationFailsEvenWhenTestFailsInExpectedManner() throws RemoteException {
+ Person.countPeople();
+ JUnitStaticEntityMockingControl.playback();
+ // No calls to allow verification failure
+ throw new RemoteException();
+ }
+}
diff --git a/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/Person.java b/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/Person.java
new file mode 100644
index 00000000000..65f112cd094
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/Person.java
@@ -0,0 +1,8 @@
+package org.springframework.mock.static_mock;
+
+import javax.persistence.Entity;
+
+@Entity
+public class Person {
+}
+
diff --git a/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/Person_Roo_Entity.aj b/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/Person_Roo_Entity.aj
new file mode 100644
index 00000000000..aadad7ce0f1
--- /dev/null
+++ b/org.springframework.aspects/src/test/java/org/springframework/mock/static_mock/Person_Roo_Entity.aj
@@ -0,0 +1,84 @@
+package org.springframework.mock.static_mock;
+
+privileged aspect Person_Roo_Entity {
+
+ @javax.persistence.PersistenceContext
+ transient javax.persistence.EntityManager Person.entityManager;
+
+ @javax.persistence.Id
+ @javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
+ @javax.persistence.Column(name = "id")
+ private java.lang.Long Person.id;
+
+ @javax.persistence.Version
+ @javax.persistence.Column(name = "version")
+ private java.lang.Integer Person.version;
+
+ public java.lang.Long Person.getId() {
+ return this.id;
+ }
+
+ public void Person.setId(java.lang.Long id) {
+ this.id = id;
+ }
+
+ public java.lang.Integer Person.getVersion() {
+ return this.version;
+ }
+
+ public void Person.setVersion(java.lang.Integer version) {
+ this.version = version;
+ }
+
+ @org.springframework.transaction.annotation.Transactional
+ public void Person.persist() {
+ if (this.entityManager == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ this.entityManager.persist(this);
+ }
+
+ @org.springframework.transaction.annotation.Transactional
+ public void Person.remove() {
+ if (this.entityManager == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ this.entityManager.remove(this);
+ }
+
+ @org.springframework.transaction.annotation.Transactional
+ public void Person.flush() {
+ if (this.entityManager == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ this.entityManager.flush();
+ }
+
+ @org.springframework.transaction.annotation.Transactional
+ public void Person.merge() {
+ if (this.entityManager == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ Person merged = this.entityManager.merge(this);
+ this.entityManager.flush();
+ this.id = merged.getId();
+ }
+
+ public static long Person.countPeople() {
+ javax.persistence.EntityManager em = new Person().entityManager;
+ if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ return (Long) em.createQuery("select count(o) from Person o").getSingleResult();
+ }
+
+ public static java.util.List Person.findAllPeople() {
+ javax.persistence.EntityManager em = new Person().entityManager;
+ if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ return em.createQuery("select o from Person o").getResultList();
+ }
+
+ public static Person Person.findPerson(java.lang.Long id) {
+ if (id == null) throw new IllegalArgumentException("An identifier is required to retrieve an instance of Person");
+ javax.persistence.EntityManager em = new Person().entityManager;
+ if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ return em.find(Person.class, id);
+ }
+
+ public static java.util.List Person.findPersonEntries(int firstResult, int maxResults) {
+ javax.persistence.EntityManager em = new Person().entityManager;
+ if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
+ return em.createQuery("select o from Person o").setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
+ }
+
+}