INT-3051 Derive Script Language from Extension
Make 'lang' attribute optional on a script element and attempt to derive the language from the file extension. Change return value on ScriptParser.deriveLanguageFromExtension to the language name Polishing (PR Comments)
This commit is contained in:
committed by
Gary Russell
parent
573c692957
commit
67fd4a5a60
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -14,6 +14,8 @@ package org.springframework.integration.scripting.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
@@ -22,11 +24,11 @@ import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.beans.factory.xml.XmlReaderContext;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.scripting.RefreshableResourceScriptSource;
|
||||
import org.springframework.scripting.support.StaticScriptSource;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
@@ -36,60 +38,68 @@ public abstract class AbstractScriptParser extends AbstractSingleBeanDefinitionP
|
||||
protected static final String LOCATION_ATTRIBUTE = "location";
|
||||
|
||||
protected static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean shouldGenerateIdAsFallback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected abstract String getBeanClassName(Element element);
|
||||
|
||||
|
||||
protected abstract String getScriptSourceClassName();
|
||||
|
||||
|
||||
@Override
|
||||
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
String scriptLocation = element.getAttribute(LOCATION_ATTRIBUTE);
|
||||
String scriptText = DomUtils.getTextValue(element);
|
||||
if (!(StringUtils.hasText(scriptLocation) ^ StringUtils.hasText(scriptText))) {
|
||||
parserContext.getReaderContext().error("Either the 'location' attribute or inline script text must be provided, but not both.", element);
|
||||
parserContext.getReaderContext().error(
|
||||
"Either the 'location' attribute or inline script text must be provided, but not both.", element);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Element> variableElements = DomUtils.getChildElementsByTagName(element, "variable");
|
||||
String scriptVariableGeneratorName = element.getAttribute("script-variable-generator");
|
||||
|
||||
if (StringUtils.hasText(scriptText) && (variableElements.size() > 0 || StringUtils.hasText(scriptVariableGeneratorName))) {
|
||||
parserContext.getReaderContext().error("Variable bindings or custom ScriptVariableGenerator are not allowed when using an inline groovy script. " +
|
||||
"Specify location of the script via 'location' attribute instead", element);
|
||||
if (StringUtils.hasText(scriptText)
|
||||
&& (variableElements.size() > 0 || StringUtils.hasText(scriptVariableGeneratorName))) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Variable bindings or custom ScriptVariableGenerator are not allowed when using an inline groovy script. "
|
||||
+ "Specify location of the script via 'location' attribute instead", element);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (StringUtils.hasText(scriptVariableGeneratorName) && variableElements.size() > 0) {
|
||||
parserContext.getReaderContext().error("'script-variable-generator' and 'variable' sub-elements are mutually exclusive.", element);
|
||||
parserContext.getReaderContext().error(
|
||||
"'script-variable-generator' and 'variable' sub-elements are mutually exclusive.", element);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (StringUtils.hasText(scriptLocation)) {
|
||||
builder.addConstructorArgValue(this.resolveScriptLocation(element, parserContext.getReaderContext(), scriptLocation));
|
||||
builder.addConstructorArgValue(this.resolveScriptLocation(element, parserContext.getReaderContext(),
|
||||
scriptLocation));
|
||||
}
|
||||
else {
|
||||
if (getScriptSourceClassName() != null){
|
||||
if (getScriptSourceClassName() != null) {
|
||||
builder.addConstructorArgValue(new StaticScriptSource(scriptText, getScriptSourceClassName()));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
builder.addConstructorArgValue(new StaticScriptSource(scriptText));
|
||||
}
|
||||
}
|
||||
if (!StringUtils.hasText(scriptVariableGeneratorName)) {
|
||||
BeanDefinitionBuilder scriptVariableGeneratorBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.integration.scripting.DefaultScriptVariableGenerator");
|
||||
BeanDefinitionBuilder scriptVariableGeneratorBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition("org.springframework.integration.scripting.DefaultScriptVariableGenerator");
|
||||
ManagedMap<String, Object> variableMap = new ManagedMap<String, Object>();
|
||||
for (Element childElement : variableElements) {
|
||||
String variableName = childElement.getAttribute("name");
|
||||
String variableValue = childElement.getAttribute("value");
|
||||
String variableRef = childElement.getAttribute("ref");
|
||||
if (!(StringUtils.hasText(variableValue) ^ StringUtils.hasText(variableRef))) {
|
||||
parserContext.getReaderContext().error("Exactly one of the 'ref' attribute or 'value' attribute, " +
|
||||
" is required for element " +
|
||||
IntegrationNamespaceUtils.createElementDescription(element) + ".", element);
|
||||
parserContext.getReaderContext().error(
|
||||
"Exactly one of the 'ref' attribute or 'value' attribute, " + " is required for element "
|
||||
+ IntegrationNamespaceUtils.createElementDescription(element) + ".", element);
|
||||
}
|
||||
if (StringUtils.hasText(variableValue)) {
|
||||
variableMap.put(variableName, variableValue);
|
||||
@@ -107,7 +117,7 @@ public abstract class AbstractScriptParser extends AbstractSingleBeanDefinitionP
|
||||
builder.addConstructorArgReference(scriptVariableGeneratorName);
|
||||
postProcess(builder, element, parserContext);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses may override this no-op method to provide additional configuration.
|
||||
*/
|
||||
@@ -115,19 +125,17 @@ public abstract class AbstractScriptParser extends AbstractSingleBeanDefinitionP
|
||||
}
|
||||
|
||||
private Object resolveScriptLocation(Element element, XmlReaderContext readerContext, String scriptLocation) {
|
||||
String refreshDelayText = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
|
||||
String beanClassName = "org.springframework.integration.scripting.RefreshableResourceScriptSource";
|
||||
BeanDefinitionBuilder resourceScriptSourceBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(beanClassName);
|
||||
String refreshDelayText = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
|
||||
String beanClassName = RefreshableResourceScriptSource.class.getName();
|
||||
BeanDefinitionBuilder resourceScriptSourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(beanClassName);
|
||||
resourceScriptSourceBuilder.addConstructorArgValue(scriptLocation);
|
||||
if (StringUtils.hasText(refreshDelayText)) {
|
||||
resourceScriptSourceBuilder.addConstructorArgValue(refreshDelayText);
|
||||
}
|
||||
else {
|
||||
resourceScriptSourceBuilder.addConstructorArgValue(-1L);
|
||||
resourceScriptSourceBuilder.addConstructorArgValue(-1L);
|
||||
}
|
||||
return resourceScriptSourceBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -16,26 +16,30 @@
|
||||
|
||||
package org.springframework.integration.scripting.config.jsr223;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.scripting.config.AbstractScriptParser;
|
||||
import org.springframework.integration.scripting.jsr223.ScriptExecutorFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.w3c.dom.Element;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ScriptParser extends AbstractScriptParser {
|
||||
|
||||
|
||||
private static final String LANGUAGE_ATTRIBUTE = "lang";
|
||||
|
||||
@Override
|
||||
protected String getBeanClassName(Element element) {
|
||||
return "org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor";
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.config.xml.AbstractScriptParser#getScriptSourceClassName()
|
||||
*/
|
||||
@@ -44,11 +48,52 @@ public class ScriptParser extends AbstractScriptParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext){
|
||||
@Override
|
||||
protected void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
|
||||
String language = element.getAttribute(LANGUAGE_ATTRIBUTE);
|
||||
Assert.hasLength(language, "Attribute " + LANGUAGE_ATTRIBUTE + " is required");
|
||||
String scriptLocation = element.getAttribute(LOCATION_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(language)) {
|
||||
if (!StringUtils.hasText(scriptLocation)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"An inline script requires the '" + LANGUAGE_ATTRIBUTE + "' attribute.", element);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
language = getLanguageFromFileExtension(scriptLocation, parserContext, element);
|
||||
if (language == null) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Unable to determine language for script '" + scriptLocation + "'", element);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.addConstructorArgValue(ScriptExecutorFactory.getScriptExecutor(language));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param scriptLocation
|
||||
* @param parserContext
|
||||
* @param element
|
||||
* @return the language
|
||||
*/
|
||||
private String getLanguageFromFileExtension(String scriptLocation, ParserContext parserContext, Element element) {
|
||||
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||
ScriptEngine engine = null;
|
||||
|
||||
int index = scriptLocation.lastIndexOf(".") + 1;
|
||||
if (index < 1) {
|
||||
return null;
|
||||
}
|
||||
String extension = scriptLocation.substring(index);
|
||||
|
||||
engine = engineManager.getEngineByExtension(extension);
|
||||
|
||||
if (engine == null) {
|
||||
parserContext.getReaderContext().error(
|
||||
"No suitable scripting engine found for extension '" + extension + "'", element);
|
||||
}
|
||||
|
||||
return engine.getFactory().getLanguageName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
<xsd:complexType name="Jsr223Script">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="ScriptType">
|
||||
<xsd:attribute name="lang" use="required">
|
||||
<xsd:attribute name="lang" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The script language or JSR 223 scripting engine name
|
||||
The script language or JSR 223 scripting engine name. Required only for inline scripts. If a script location is
|
||||
referenced, the language may be derived from the file extension
|
||||
(.rb: ruby, .groovy, js: javascript (ECMAScript), .py: python).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int-script="http://www.springframework.org/schema/integration/scripting"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/scripting http://www.springframework.org/schema/integration/scripting/spring-integration-scripting.xsd">
|
||||
|
||||
<int-script:script location="foo.rb"/>
|
||||
<int-script:script location="foo.groovy"/>
|
||||
<int-script:script location="foo.js"/>
|
||||
<int-script:script location="foo.py"/>
|
||||
</beans>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int-script="http://www.springframework.org/schema/integration/scripting"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/scripting http://www.springframework.org/schema/integration/scripting/spring-integration-scripting.xsd">
|
||||
|
||||
<int-script:script location="foo.xx"/>
|
||||
</beans>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int-script="http://www.springframework.org/schema/integration/scripting"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/scripting http://www.springframework.org/schema/integration/scripting/spring-integration-scripting.xsd">
|
||||
|
||||
<int-script:script location="foo"/>
|
||||
</beans>
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.scripting.jsr223;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class DeriveLanguageFromExtensionTests {
|
||||
@Autowired
|
||||
private ApplicationContext ctx;
|
||||
|
||||
@Test
|
||||
public void testParseLanguage() {
|
||||
String langs[] = { "ruby", "Groovy", "ECMAScript", "python" };
|
||||
Class<?> executors[] = {
|
||||
RubyScriptExecutor.class,
|
||||
DefaultScriptExecutor.class,
|
||||
DefaultScriptExecutor.class,
|
||||
PythonScriptExecutor.class
|
||||
};
|
||||
|
||||
Map<String, ScriptExecutingMessageProcessor> scriptProcessors = ctx
|
||||
.getBeansOfType(ScriptExecutingMessageProcessor.class);
|
||||
assertEquals(4, scriptProcessors.size());
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
||||
ScriptExecutingMessageProcessor processor = ctx.getBean(
|
||||
"org.springframework.integration.scripting.jsr223.ScriptExecutingMessageProcessor#" + i,
|
||||
ScriptExecutingMessageProcessor.class);
|
||||
|
||||
AbstractScriptExecutor executor = (AbstractScriptExecutor) TestUtils.getPropertyValue(processor,
|
||||
"scriptExecutor");
|
||||
assertEquals(langs[i], executor.language);
|
||||
assertEquals(executors[i], executor.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadExtension() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail1-context.xml", this.getClass());
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertTrue(e.getMessage().contains("No suitable scripting engine found for extension 'xx'"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoExtension() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail2-context.xml", this.getClass());
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertTrue(e.getMessage().contains("Unable to determine language for script 'foo'"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user