/*
* Copyright 2002-2008 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.scripting.support;
import junit.framework.TestCase;
import org.easymock.MockControl;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.scripting.Messenger;
import org.springframework.scripting.ScriptCompilationException;
import org.springframework.scripting.groovy.GroovyScriptFactory;
/**
* @author Rick Evans
* @author Juergen Hoeller
*/
public class ScriptFactoryPostProcessorTests extends TestCase {
private static final String MESSAGE_TEXT = "Bingo";
private static final String MESSENGER_BEAN_NAME = "messenger";
private static final String PROCESSOR_BEAN_NAME = "processor";
private static final String CHANGED_SCRIPT = "package org.springframework.scripting.groovy\n" +
"import org.springframework.scripting.Messenger\n" +
"class GroovyMessenger implements Messenger {\n" +
" private String message = \"Bingo\"\n" +
" public String getMessage() {\n" +
// quote the returned message (this is the change)...
" return \"'\" + this.message + \"'\"\n" +
" }\n" +
" public void setMessage(String message) {\n" +
" this.message = message\n" +
" }\n" +
"}";
private static final String EXPECTED_CHANGED_MESSAGE_TEXT = "'" + MESSAGE_TEXT + "'";
private static final int DEFAULT_SECONDS_TO_PAUSE = 1;
private static final String DELEGATING_SCRIPT = "inline:package org.springframework.scripting;\n" +
"class DelegatingMessenger implements Messenger {\n" +
" private Messenger wrappedMessenger;\n" +
" public String getMessage() {\n" +
" return this.wrappedMessenger.getMessage()\n" +
" }\n" +
" public void setMessenger(Messenger wrappedMessenger) {\n" +
" this.wrappedMessenger = wrappedMessenger\n" +
" }\n" +
"}";
public void testDoesNothingWhenPostProcessingNonScriptFactoryTypeBeforeInstantiation() throws Exception {
assertNull(new ScriptFactoryPostProcessor().postProcessBeforeInstantiation(getClass(), "a.bean"));
}
public void testThrowsExceptionIfGivenNonAbstractBeanFactoryImplementation() throws Exception {
MockControl mock = MockControl.createControl(BeanFactory.class);
mock.replay();
try {
new ScriptFactoryPostProcessor().setBeanFactory((BeanFactory) mock.getMock());
fail("Must have thrown exception by this point.");
}
catch (IllegalStateException expected) {
}
mock.verify();
}
public void testChangeScriptWithRefreshableBeanFunctionality() throws Exception {
BeanDefinition processorBeanDefinition = createScriptFactoryPostProcessor(true);
BeanDefinition scriptedBeanDefinition = createScriptedGroovyBean();
GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBeanDefinition(PROCESSOR_BEAN_NAME, processorBeanDefinition);
ctx.registerBeanDefinition(MESSENGER_BEAN_NAME, scriptedBeanDefinition);
ctx.refresh();
Messenger messenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
assertEquals(MESSAGE_TEXT, messenger.getMessage());
// cool; now let's change the script and check the refresh behaviour...
pauseToLetRefreshDelayKickIn(DEFAULT_SECONDS_TO_PAUSE);
StaticScriptSource source = getScriptSource(ctx);
source.setScript(CHANGED_SCRIPT);
Messenger refreshedMessenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
// the updated script surrounds the message in quotes before returning...
assertEquals(EXPECTED_CHANGED_MESSAGE_TEXT, refreshedMessenger.getMessage());
}
public void testChangeScriptWithNoRefreshableBeanFunctionality() throws Exception {
BeanDefinition processorBeanDefinition = createScriptFactoryPostProcessor(false);
BeanDefinition scriptedBeanDefinition = createScriptedGroovyBean();
GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBeanDefinition(PROCESSOR_BEAN_NAME, processorBeanDefinition);
ctx.registerBeanDefinition(MESSENGER_BEAN_NAME, scriptedBeanDefinition);
ctx.refresh();
Messenger messenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
assertEquals(MESSAGE_TEXT, messenger.getMessage());
// cool; now let's change the script and check the refresh behaviour...
pauseToLetRefreshDelayKickIn(DEFAULT_SECONDS_TO_PAUSE);
StaticScriptSource source = getScriptSource(ctx);
source.setScript(CHANGED_SCRIPT);
Messenger refreshedMessenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
assertEquals("Script seems to have been refreshed (must not be as no refreshCheckDelay set on ScriptFactoryPostProcessor)",
MESSAGE_TEXT, refreshedMessenger.getMessage());
}
public void testRefreshedScriptReferencePropagatesToCollaborators() throws Exception {
BeanDefinition processorBeanDefinition = createScriptFactoryPostProcessor(true);
BeanDefinition scriptedBeanDefinition = createScriptedGroovyBean();
BeanDefinitionBuilder collaboratorBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultMessengerService.class);
collaboratorBuilder.addPropertyReference(MESSENGER_BEAN_NAME, MESSENGER_BEAN_NAME);
GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBeanDefinition(PROCESSOR_BEAN_NAME, processorBeanDefinition);
ctx.registerBeanDefinition(MESSENGER_BEAN_NAME, scriptedBeanDefinition);
final String collaboratorBeanName = "collaborator";
ctx.registerBeanDefinition(collaboratorBeanName, collaboratorBuilder.getBeanDefinition());
ctx.refresh();
Messenger messenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
assertEquals(MESSAGE_TEXT, messenger.getMessage());
// cool; now let's change the script and check the refresh behaviour...
pauseToLetRefreshDelayKickIn(DEFAULT_SECONDS_TO_PAUSE);
StaticScriptSource source = getScriptSource(ctx);
source.setScript(CHANGED_SCRIPT);
Messenger refreshedMessenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
// the updated script surrounds the message in quotes before returning...
assertEquals(EXPECTED_CHANGED_MESSAGE_TEXT, refreshedMessenger.getMessage());
// ok, is this change reflected in the reference that the collaborator has?
DefaultMessengerService collaborator = (DefaultMessengerService) ctx.getBean(collaboratorBeanName);
assertEquals(EXPECTED_CHANGED_MESSAGE_TEXT, collaborator.getMessage());
}
public void testReferencesAcrossAContainerHierarchy() throws Exception {
GenericApplicationContext businessContext = new GenericApplicationContext();
businessContext.registerBeanDefinition("messenger", BeanDefinitionBuilder.rootBeanDefinition(StubMessenger.class).getBeanDefinition());
businessContext.refresh();
BeanDefinitionBuilder scriptedBeanBuilder = BeanDefinitionBuilder.rootBeanDefinition(GroovyScriptFactory.class);
scriptedBeanBuilder.addConstructorArgValue(DELEGATING_SCRIPT);
scriptedBeanBuilder.addPropertyReference("messenger", "messenger");
GenericApplicationContext presentationCtx = new GenericApplicationContext(businessContext);
presentationCtx.registerBeanDefinition("needsMessenger", scriptedBeanBuilder.getBeanDefinition());
presentationCtx.registerBeanDefinition("scriptProcessor", createScriptFactoryPostProcessor(true));
presentationCtx.refresh();
}
public void testScriptHavingAReferenceToAnotherBean() throws Exception {
// just tests that the (singleton) script-backed bean is able to be instantiated with references to its collaborators
new ClassPathXmlApplicationContext("org/springframework/scripting/support/groovyReferences.xml");
}
public void testForRefreshedScriptHavingErrorPickedUpOnFirstCall() throws Exception {
BeanDefinition processorBeanDefinition = createScriptFactoryPostProcessor(true);
BeanDefinition scriptedBeanDefinition = createScriptedGroovyBean();
BeanDefinitionBuilder collaboratorBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultMessengerService.class);
collaboratorBuilder.addPropertyReference(MESSENGER_BEAN_NAME, MESSENGER_BEAN_NAME);
GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBeanDefinition(PROCESSOR_BEAN_NAME, processorBeanDefinition);
ctx.registerBeanDefinition(MESSENGER_BEAN_NAME, scriptedBeanDefinition);
final String collaboratorBeanName = "collaborator";
ctx.registerBeanDefinition(collaboratorBeanName, collaboratorBuilder.getBeanDefinition());
ctx.refresh();
Messenger messenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
assertEquals(MESSAGE_TEXT, messenger.getMessage());
// cool; now let's change the script and check the refresh behaviour...
pauseToLetRefreshDelayKickIn(DEFAULT_SECONDS_TO_PAUSE);
StaticScriptSource source = getScriptSource(ctx);
// needs The Sundays compiler; must NOT throw any exception here...
source.setScript("I keep hoping you are the same as me, and I'll send you letters and come to your house for tea");
Messenger refreshedMessenger = (Messenger) ctx.getBean(MESSENGER_BEAN_NAME);
try {
refreshedMessenger.getMessage();
fail("Must have thrown an Exception (invalid script)");
}
catch (FatalBeanException expected) {
assertTrue(expected.contains(ScriptCompilationException.class));
}
}
public void testPrototypeScriptedBean() throws Exception {
GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBeanDefinition("messenger", BeanDefinitionBuilder.rootBeanDefinition(StubMessenger.class).getBeanDefinition());
BeanDefinitionBuilder scriptedBeanBuilder = BeanDefinitionBuilder.rootBeanDefinition(GroovyScriptFactory.class);
scriptedBeanBuilder.setSingleton(false);
scriptedBeanBuilder.addConstructorArgValue(DELEGATING_SCRIPT);
scriptedBeanBuilder.addPropertyReference("messenger", "messenger");
final String BEAN_WITH_DEPENDENCY_NAME = "needsMessenger";
ctx.registerBeanDefinition(BEAN_WITH_DEPENDENCY_NAME, scriptedBeanBuilder.getBeanDefinition());
ctx.registerBeanDefinition("scriptProcessor", createScriptFactoryPostProcessor(true));
ctx.refresh();
Messenger messenger1 = (Messenger) ctx.getBean(BEAN_WITH_DEPENDENCY_NAME);
Messenger messenger2 = (Messenger) ctx.getBean(BEAN_WITH_DEPENDENCY_NAME);
assertNotSame(messenger1, messenger2);
}
private static StaticScriptSource getScriptSource(GenericApplicationContext ctx) throws Exception {
ScriptFactoryPostProcessor processor = (ScriptFactoryPostProcessor) ctx.getBean(PROCESSOR_BEAN_NAME);
BeanDefinition bd = processor.scriptBeanFactory.getBeanDefinition("scriptedObject.messenger");
return (StaticScriptSource) bd.getConstructorArgumentValues().getIndexedArgumentValue(0, StaticScriptSource.class).getValue();
}
private static BeanDefinition createScriptFactoryPostProcessor(boolean isRefreshable) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ScriptFactoryPostProcessor.class);
if (isRefreshable) {
builder.addPropertyValue("defaultRefreshCheckDelay", new Long(1));
}
return builder.getBeanDefinition();
}
private static BeanDefinition createScriptedGroovyBean() {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(GroovyScriptFactory.class);
builder.addConstructorArgValue("inline:package org.springframework.scripting;\n" +
"class GroovyMessenger implements Messenger {\n" +
" private String message = \"Bingo\"\n" +
" public String getMessage() {\n" +
" return this.message\n" +
" }\n" +
" public void setMessage(String message) {\n" +
" this.message = message\n" +
" }\n" +
"}");
builder.addPropertyValue("message", MESSAGE_TEXT);
return builder.getBeanDefinition();
}
private static void pauseToLetRefreshDelayKickIn(int secondsToPause) {
try {
Thread.sleep(secondsToPause * 1000);
}
catch (InterruptedException ignored) {
}
}
public static class DefaultMessengerService {
private Messenger messenger;
public void setMessenger(Messenger messenger) {
this.messenger = messenger;
}
public String getMessage() {
return this.messenger.getMessage();
}
}
}