/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* 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 com.alibaba.citrus.service.moduleloader.impl.factory;
import static com.alibaba.citrus.springext.util.DomUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static com.alibaba.citrus.util.regex.PathNameWildcardCompiler.*;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.regex.Pattern;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionDefaults;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.scripting.config.LangNamespaceUtils;
import org.w3c.dom.Element;
/**
* 解析script-modules。
*
* @author Michael Zhou
*/
public class ScriptModuleFactoryDefinitionParser extends AbstractModuleFactoryDefinitionParser<ScriptModuleFactory> {
private static final String LANG_URI = "http://www.springframework.org/schema/lang";
private static final String SCRIPT_SOURCE_ATTRIBUTE = "script-source";
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
Map<String, ParsingModuleInfo> scripts = parseSpecificBeans(element, parserContext,
builder.getRawBeanDefinition(), ns(LANG_URI));
ElementSelector searchFolders = and(sameNs(element), name("search-folders"));
ElementSelector searchFiles = and(sameNs(element), name("search-files"));
for (Element subElement : subElements(element)) {
String prefix = null;
String typeName = null;
String moduleName = null;
Pattern scriptNamePattern = null;
String scriptResourceName = null;
String language = null;
if (searchFolders.accept(subElement)) {
String folderName = assertNotNull(normalizePathName(subElement.getAttribute("folders")),
"no folder name provided for search-folders");
// 取prefix
prefix = getPrefix(folderName);
if (prefix != null) {
folderName = folderName.substring(prefix.length() + 1);
}
// folderName不以/开始
if (folderName.startsWith("/")) {
folderName = folderName.substring(1);
}
scriptNamePattern = compilePathName(folderName);
typeName = assertNotNull(trimToNull(subElement.getAttribute("type")), "no type name provided");
language = trimToNull(subElement.getAttribute("language"));
scriptResourceName = folderName + "/**/*.*";
log.trace("Searching in folders: {}, moduleType={}, language={}", new Object[] { folderName, typeName,
language == null ? "auto" : language });
} else if (searchFiles.accept(subElement)) {
String fileName = assertNotNull(normalizePathName(subElement.getAttribute("files")),
"no script file name provided for search-files");
// fileName不以/结尾
assertTrue(!fileName.endsWith("/"), "invalid script file name: %s", fileName);
// 取prefix
prefix = getPrefix(fileName);
if (prefix != null) {
fileName = fileName.substring(prefix.length() + 1);
}
// fileName不以/开始
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}
scriptNamePattern = compilePathName(fileName);
typeName = assertNotNull(trimToNull(subElement.getAttribute("type")), "no type name provided");
moduleName = assertNotNull(trimToNull(subElement.getAttribute("name")), "no module name provided");
language = trimToNull(subElement.getAttribute("language"));
scriptResourceName = fileName;
log.trace("Searching for script files: {}, moduleType={}, moduleName={}, language={}", new Object[] {
fileName, typeName, moduleName, language == null ? "auto" : language });
}
if (scriptResourceName != null) {
scriptResourceName = prefix == null ? scriptResourceName : prefix + ":" + scriptResourceName;
// 扫描scripts
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(parserContext
.getReaderContext().getResourceLoader());
int found = 0;
try {
Resource[] resources = resolver.getResources(scriptResourceName.replace('?', '*'));
BeanDefinitionDefaults defaults = getBeanDefinitionDefaults(subElement, parserContext);
ParsingModuleMatcher matcher = new ParsingModuleMatcher(scripts, scriptNamePattern, typeName,
moduleName) {
@Override
protected String getName(String name, String itemName) {
String ext = getExt(itemName);
if (ext != null && name.endsWith("." + ext)) {
return name.substring(0, name.length() - ext.length() - 1);
}
return name;
}
};
for (Resource resource : resources) {
if (resource.isReadable()) {
URI uri = resource.getURI();
if (uri == null) {
continue;
}
String resourceName = uri.normalize().toString();
if (!resourceName.endsWith(".class") && matcher.doMatch(resourceName)) {
BeanDefinition scriptBean = createScriptBean(subElement, parserContext, resourceName,
language, defaults);
String beanName = matcher.generateBeanName(resourceName, parserContext.getRegistry());
parserContext.getRegistry().registerBeanDefinition(beanName, scriptBean);
found++;
}
}
}
} catch (IOException e) {
parserContext.getReaderContext().error("Failed to scan resources: " + scriptResourceName,
subElement, e);
return;
}
log.debug("Found {} module scripts with pattern: {}", found, scriptResourceName);
}
}
postProcessItems(element, parserContext, builder, scripts, "search-folders or search-files");
}
private String getPrefix(String name) {
String prefix = null;
int index = name.indexOf(":");
if (index >= 0) {
prefix = name.substring(0, index);
assertTrue(!prefix.contains("*") && !prefix.contains("?"), "invalid folder or file name: %s", name);
}
return prefix;
}
private BeanDefinition createScriptBean(Element element, ParserContext parserContext, String resource,
String language, BeanDefinitionDefaults defaults) {
LangNamespaceUtils.registerScriptFactoryPostProcessorIfNecessary(parserContext.getRegistry());
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getScriptFactoryClassName(resource,
language));
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
builder.addConstructorArgValue(resource);
AbstractBeanDefinition bd = builder.getBeanDefinition();
bd.applyDefaults(defaults);
return bd;
}
private String getScriptFactoryClassName(String resource, String language) {
if (language == null) {
language = getExt(resource);
}
assertNotNull(language, "Could not determine the script language: %s", resource);
language = language.toLowerCase();
if ("groovy".equals(language)) {
return "org.springframework.scripting.groovy.GroovyScriptFactory";
} else if ("jruby".equals(language) || "ruby".equals(language)) {
return "org.springframework.scripting.jruby.JRubyScriptFactory";
} else if ("bsh".equals(language)) {
return "org.springframework.scripting.bsh.BshScriptFactory";
} else {
throw new IllegalArgumentException("Unsupported script language: " + language);
}
}
private String getExt(String name) {
name = name.substring(name.lastIndexOf("/") + 1);
int index = name.lastIndexOf(".");
String ext = null;
if (index > 0) {
ext = name.substring(index + 1);
}
return ext;
}
/**
* 对script-modules,默认lazy-init为true,这是为了防止创建抽象类,导致初始化失败。(class-
* modules不存在这样的问题。)
*/
@Override
protected boolean getDefaultLazyInit() {
return true;
}
@Override
protected String parseItemName(ParserContext parserContext, Element element, BeanDefinition bd) {
String resourceName = assertNotNull(trimToNull(element.getAttribute(SCRIPT_SOURCE_ATTRIBUTE)),
"Missing Attribute: %s", SCRIPT_SOURCE_ATTRIBUTE);
Resource resource = parserContext.getReaderContext().getResourceLoader().getResource(resourceName);
try {
return resource.getURI().normalize().toString();
} catch (IOException e) {
parserContext.getReaderContext().error("Failed to get resource: " + resourceName, element, e);
return null;
}
}
@Override
protected String getDefaultName() {
return "scriptModuleFactory";
}
}