/*
* Copyright 2002-2016 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.core.io.AbstractResource;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.scripting.RefreshableResourceScriptSource;
import org.springframework.integration.scripting.ScriptVariableGenerator;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.test.annotation.Repeat;
import groovy.lang.Script;
/**
* @author Mark Fisher
* @author Dave Syer
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
public class GroovyScriptExecutingMessageProcessorTests {
@Rule
public RepeatProcessor repeater = new RepeatProcessor(4);
private final AtomicInteger countHolder = new AtomicInteger();
@Test
@Repeat(20)
public void testSimpleExecution() throws Exception {
int count = countHolder.getAndIncrement();
String script = "return \"payload is $payload, header is $headers.testHeader\"";
Message<?> message = MessageBuilder.withPayload("foo").setHeader("testHeader", "bar" + count).build();
TestResource resource = new TestResource(script, "simpleTest");
ScriptSource scriptSource = new ResourceScriptSource(resource);
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
Object result = processor.processMessage(message);
assertEquals("payload is foo, header is bar" + count, result.toString());
}
@Test
public void testSimpleExecutionWithScriptVariableGenerator() throws Exception {
int count = countHolder.getAndIncrement();
String script = "return \"payload is $payload, header is $headers.testHeader and date is $date\"";
Message<?> message = MessageBuilder.withPayload("foo").setHeader("testHeader", "bar" + count).build();
TestResource resource = new TestResource(script, "simpleTest");
ScriptSource scriptSource = new ResourceScriptSource(resource);
Object result = null;
class CustomScriptVariableGenerator implements ScriptVariableGenerator {
@Override
public Map<String, Object> generateScriptVariables(Message<?> message) {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("date", System.nanoTime());
variables.put("payload", message.getPayload());
variables.put("headers", message.getHeaders());
return variables;
}
}
for (int i = 0; i < 5; i++) {
ScriptVariableGenerator scriptVariableGenerator = new CustomScriptVariableGenerator();
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource, scriptVariableGenerator);
Object newResult = processor.processMessage(message);
assertFalse(newResult.equals(result)); // make sure that we get different nanotime verifying that generateScriptVariables() is invoked
result = newResult;
}
}
@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, 2000L);
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
// should be the original script
Object result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
//reset the script with the new strimg
resource.setScript("return \"payload is $payload\"");
Thread.sleep(20L);
// should still assert to the old script because not enough time elapsed for refresh to kick in
result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
// sleep some more, past refresh time
Thread.sleep(2000L);
// now you go the new script
resource.setScript("return \"payload is $payload\"");
result = processor.processMessage(message);
assertEquals("payload is foo", 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);
// process with the first script
Object result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
// change script, but since refresh-delay is less then 0 we should still se old script executing
resource.setScript("return \"payload is $payload\"");
// process and see assert that the old script is used
result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
}
@Test
public void testRefreshableScriptExecutionWithAlwaysRefresh() 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, 0);
MessageProcessor<Object> processor = new GroovyScriptExecutingMessageProcessor(scriptSource);
// process with the first script
Object result = processor.processMessage(message);
assertEquals("payload is foo, header is bar", result.toString());
// change script, but since refresh-delay is less then 0 we should still se old script executing
resource.setScript("return \"payload is $payload\"");
// process and see assert that the old script is used
result = processor.processMessage(message);
assertEquals("payload is foo", result.toString());
resource.setScript("return \"payload is 'hello'\"");
// process and see assert that the old script is used
result = processor.processMessage(message);
assertEquals("payload is 'hello'", result.toString());
}
@Test
public void testInt3166GroovyScriptExecutingMessageProcessorPerformance() throws Exception {
final Message<?> message = new GenericMessage<Object>("test");
final AtomicInteger var1 = new AtomicInteger();
final AtomicInteger var2 = new AtomicInteger();
String script =
"var1.incrementAndGet(); Thread.sleep(100); var2.set(Math.max(var1.get(), var2.get())); var1.decrementAndGet()";
ScriptSource scriptSource = new StaticScriptSource(script, Script.class.getName());
final MessageProcessor<Object> processor =
new GroovyScriptExecutingMessageProcessor(scriptSource, message1 -> {
Map<String, Object> variables = new HashMap<String, Object>(2);
variables.put("var1", var1);
variables.put("var2", var2);
return variables;
});
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.execute(() -> processor.processMessage(message));
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertTrue(var2.get() > 1);
}
private static class TestResource extends AbstractResource {
private volatile String script;
private final String filename;
private volatile long lastModified;
private TestResource(String script, String filename) {
setScript(script);
this.filename = filename;
}
@Override
public long lastModified() throws IOException {
return lastModified;
}
public void setScript(String script) {
this.lastModified = System.nanoTime();
this.script = script;
}
@Override
public String getDescription() {
return "test";
}
@Override
public String getFilename() {
return this.filename;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(script.getBytes("UTF-8"));
}
}
}