INT-1381: add refresh check delay via script source wrapper

This commit is contained in:
David Syer
2010-08-31 16:23:45 +00:00
parent de21e3f201
commit 041ddbdf37
7 changed files with 363 additions and 15 deletions

View File

@@ -33,6 +33,7 @@ import org.springframework.util.xml.DomUtils;
public class GroovyScriptParser extends AbstractSingleBeanDefinitionParser {
private static final String LOCATION_ATTRIBUTE = "location";
private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
@Override
@@ -53,9 +54,16 @@ public class GroovyScriptParser extends AbstractSingleBeanDefinitionParser {
return null;
}
else if (hasScriptLocation) {
String refreshDelayText = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
String beanClassName = "org.springframework.integration.groovy.config.RefreshableResourceScriptSource";
BeanDefinitionBuilder resourceScriptSourceBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.scripting.support.ResourceScriptSource");
BeanDefinitionBuilder.genericBeanDefinition(beanClassName);
resourceScriptSourceBuilder.addConstructorArgValue(element.getAttribute(LOCATION_ATTRIBUTE));
if (StringUtils.hasText(refreshDelayText)) {
resourceScriptSourceBuilder.addConstructorArgValue(refreshDelayText);
} else {
resourceScriptSourceBuilder.addConstructorArgValue(-1L);
}
return resourceScriptSourceBuilder.getBeanDefinition();
}
return new StaticScriptSource(scriptText, "groovy.lang.Script");

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2010 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.integration.groovy.config;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.core.io.Resource;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.support.ResourceScriptSource;
/**
* @author Dave Syer
*
* @since 2.0
*
*/
public class RefreshableResourceScriptSource implements ScriptSource {
private final long refreshDelay;
private final ResourceScriptSource source;
private AtomicLong lastModifiedChecked = new AtomicLong(System.currentTimeMillis());
private String script;
public RefreshableResourceScriptSource(Resource resource, long refreshDelay) {
this.refreshDelay = refreshDelay;
this.source = new ResourceScriptSource(resource);
try {
this.script = source.getScriptAsString();
}
catch (IOException e) {
lastModifiedChecked.set(0);
}
}
public String getScriptAsString() throws IOException {
if (isModified()) {
this.script = source.getScriptAsString();
}
return script;
}
public boolean isModified() {
if (refreshDelay < 0) {
return false;
}
long time = System.currentTimeMillis();
if (refreshDelay == 0 || (time - lastModifiedChecked.get()) > refreshDelay) {
lastModifiedChecked.set(time);
return source.isModified();
}
return false;
}
public String suggestedClassName() {
return source.suggestedClassName();
}
}

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration/groovy"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/groovy"
@@ -9,13 +8,13 @@
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd" />
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd" />
<xsd:element name="script">
<xsd:annotation>
<xsd:documentation>
Configures an inner bean that will generate a Groovy Script.
</xsd:documentation>
Configures an inner bean that will generate a Groovy Script.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:simpleContent>
@@ -23,8 +22,16 @@
<xsd:attribute name="location">
<xsd:annotation>
<xsd:documentation>
Resource location path for the Script. Either this or an inline script
as body text should be provided, but not both.
Resource location path for the Script. Either this or an inline script
as body text should be provided, but not both.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="refresh-check-delay">
<xsd:annotation>
<xsd:documentation>
Refresh delay for the script contents if specified as a resource
location (defaults to never refresh).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -17,15 +17,16 @@
package org.springframework.integration.groovy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
import org.springframework.core.io.AbstractResource;
import org.springframework.integration.Message;
import org.springframework.integration.groovy.config.RefreshableResourceScriptSource;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.scripting.ScriptSource;
@@ -38,12 +39,60 @@ import org.springframework.scripting.support.ResourceScriptSource;
public class GroovyScriptExecutingMessageProcessorTests {
@Test
public void simpleTest() throws Exception {
public void testSimpleExecution() throws Exception {
String script = "return \"payload is $payload, header is $headers.testHeader\"";
Message<?> message = MessageBuilder.withPayload("foo").setHeader("testHeader", "bar").build();
TestResource resource = new TestResource(script, "simpleTest");
ScriptSource scriptSource = new ResourceScriptSource(resource);
MessageProcessor processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
Object result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
}
@Test
public void testLastModified() throws Exception {
String script = "return \"payload is $payload, header is $headers.testHeader\"";
TestResource resource = new TestResource(script, "simpleTest");
long lastModified = resource.lastModified();
Thread.sleep(20L);
resource.setScript("foo");
assertFalse("Expected last modified to change: "+lastModified+"=="+resource.lastModified(), lastModified==resource.lastModified());
}
@Test
public void testModifiedScriptExecution() throws Exception {
String script = "return \"payload is $payload, header is $headers.testHeader\"";
Message<?> message = MessageBuilder.withPayload("foo").setHeader("testHeader", "bar").build();
TestResource resource = new TestResource(script, "simpleTest");
ScriptSource scriptSource = new ResourceScriptSource(resource);
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
Thread.sleep(20L);
resource.setScript("return \"payload is $payload\"");
Object result = processor.processMessage(message);
assertEquals("payload is foo", result.toString());
}
@Test
public void testRefreshableScriptExecution() throws Exception {
String script = "return \"payload is $payload, header is $headers.testHeader\"";
Message<?> message = MessageBuilder.withPayload("foo").setHeader("testHeader", "bar").build();
TestResource resource = new TestResource(script, "simpleTest");
ScriptSource scriptSource = new RefreshableResourceScriptSource(resource, 1000L);
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
Thread.sleep(20L);
resource.setScript("return \"payload is $payload\"");
Object result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
}
@Test
public void testRefreshableScriptExecutionWithInfiniteDelay() throws Exception {
String script = "return \"payload is $payload, header is $headers.testHeader\"";
Message<?> message = MessageBuilder.withPayload("foo").setHeader("testHeader", "bar").build();
TestResource resource = new TestResource(script, "simpleTest");
ScriptSource scriptSource = new RefreshableResourceScriptSource(resource, -1L);
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
resource.setScript("return \"payload is $payload\"");
Object result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
}
@@ -51,15 +100,26 @@ public class GroovyScriptExecutingMessageProcessorTests {
private static class TestResource extends AbstractResource {
private final String scriptString;
private String script;
private final String filename;
private TestResource(String scriptString, String filename) {
this.scriptString = scriptString;
private long lastModified;
private TestResource(String script, String filename) {
setScript(script);
this.filename = filename;
}
public long lastModified() throws IOException {
return lastModified;
}
public void setScript(String script) {
this.lastModified = System.currentTimeMillis();
this.script = script;
}
public String getDescription() {
return "test";
}
@@ -70,7 +130,7 @@ public class GroovyScriptExecutingMessageProcessorTests {
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(scriptString.getBytes("UTF-8"));
return new ByteArrayInputStream(script.getBytes("UTF-8"));
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:groovy="http://www.springframework.org/schema/integration/groovy"
xsi:schemaLocation="http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/groovy
http://www.springframework.org/schema/integration/groovy/spring-integration-groovy.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer" xmlns="http://www.springframework.org/schema/beans">
<property name="customEditors">
<map>
<entry key="org.springframework.core.io.Resource" value="org.springframework.integration.groovy.config.GroovyRefreshTests$ResourceEditor"/>
</map>
</property>
</bean>
<transformer input-channel="referencedScriptInput">
<groovy:script location="GroovyRefreshTests.groovy" refresh-check-delay="0"/>
</transformer>
</beans:beans>

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2002-2010 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.integration.groovy.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.beans.PropertyEditorSupport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.AbstractResource;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @since 2.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class GroovyRefreshTests {
@Autowired
private MessageChannel referencedScriptInput;
@Test
public void referencedScript() throws Exception {
QueueChannel replyChannel = new QueueChannel();
replyChannel.setBeanName("returnAddress");
this.referencedScriptInput.send(MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build());
assertEquals("groovy-test-1", replyChannel.receive(0).getPayload());
this.referencedScriptInput.send(MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build());
assertEquals("groovy-test-0", replyChannel.receive(0).getPayload());
assertNull(replyChannel.receive(0));
}
public static class ResourceEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
super.setValue(new CycleResource());
}
}
private static class CycleResource extends AbstractResource {
private int count = -1;
private String[] scripts = {"\"groovy-$payload-0\"", "\"groovy-$payload-1\""};
public String getDescription() {
return "CycleResource";
}
@Override
public String getFilename() throws IllegalStateException {
return "CycleResource";
}
@Override
public long lastModified() throws IOException {
return -1;
}
public InputStream getInputStream() throws IOException {
if (++count>scripts.length-1) {
count = 0;
}
return new ByteArrayInputStream(scripts[count].getBytes());
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-2010 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.integration.groovy.config;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import org.junit.Test;
import org.springframework.core.io.ByteArrayResource;
/**
* @author Dave Syer
*
*/
public class RefreshableResourceScriptSourceTests {
@Test
public void testGetScriptAsString() throws Exception {
RefreshableResourceScriptSource source = new RefreshableResourceScriptSource(new ByteArrayResource("foo".getBytes()), 1000);
assertEquals("foo", source.getScriptAsString());
}
@Test
public void testIsModified() throws Exception {
RefreshableResourceScriptSource source = new RefreshableResourceScriptSource(new ByteArrayResource("foo".getBytes()), 1000);
assertEquals(false, source.isModified());
}
@Test
public void testIsModifiedZeroDelay() throws Exception {
RefreshableResourceScriptSource source = new RefreshableResourceScriptSource(new ByteArrayResource("foo".getBytes()) {
@Override
public long lastModified() throws IOException {
return System.currentTimeMillis();
}
}, 0);
assertEquals(false, source.isModified());
Thread.sleep(20L);
assertEquals(true, source.isModified());
assertEquals("foo", source.getScriptAsString());
}
@Test
public void testIsModifiedInfiniteDelay() throws Exception {
RefreshableResourceScriptSource source = new RefreshableResourceScriptSource(new ByteArrayResource("foo".getBytes()) {
@Override
public long lastModified() throws IOException {
return System.currentTimeMillis();
}
}, -1);
assertEquals(false, source.isModified());
assertEquals("foo", source.getScriptAsString());
}
@Test
public void testSuggestedClassName() throws Exception {
RefreshableResourceScriptSource source = new RefreshableResourceScriptSource(new ByteArrayResource("foo".getBytes()) {
@Override
public String getFilename() throws IllegalStateException {
return "Foo";
}
}, 1000);
assertEquals("Foo", source.suggestedClassName());
}
}