/*
* Copyright 2013 NGDATA nv
* Copyright 2007 Outerthought bvba and Schaubroeck nv
*
* 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.lilyproject.runtime.model;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.LogFactory;
import org.lilyproject.runtime.LilyRTException;
import org.lilyproject.runtime.LilyRuntime;
import org.lilyproject.runtime.conf.Conf;
import org.lilyproject.runtime.conf.XmlConfBuilder;
import org.lilyproject.runtime.repository.ArtifactRepository;
public class LilyRuntimeModelBuilder {
private LilyRuntimeModelBuilder() {
}
public static LilyRuntimeModel build(File runtimeConfig, Set<String> disabledModuleIds,
ArtifactRepository repository) throws Exception {
Conf conf = XmlConfBuilder.build(runtimeConfig);
return build(conf, disabledModuleIds, repository, new SourceLocations());
}
public static LilyRuntimeModel build(File runtimeConfig, Set<String> disabledModuleIds,
ArtifactRepository repository, SourceLocations artifactSourceLocations) throws Exception {
Conf conf = XmlConfBuilder.build(runtimeConfig);
return build(conf, disabledModuleIds, repository, artifactSourceLocations);
}
public static LilyRuntimeModel build(Conf runtimeConf, Set<String> disabledModuleIds,
ArtifactRepository repository, SourceLocations artifactSourceLocations) throws Exception {
LilyRuntimeModel model = new LilyRuntimeModel();
Stack<String> wiringFileStack = new Stack<String>(); // used to detect recursive wiring.xml inclusions
List<ModuleDefinition> modules = new ArrayList<ModuleDefinition>();
buildModules(modules, wiringFileStack, runtimeConf, repository, artifactSourceLocations);
// Remove disabled module entries
Iterator<ModuleDefinition> it = modules.iterator();
while (it.hasNext()) {
ModuleDefinition entry = it.next();
if (disabledModuleIds.contains(entry.getId())) {
it.remove();
}
}
for (ModuleDefinition md : modules) {
model.addModule(md);
}
return model;
}
private static void buildModules(List<ModuleDefinition> modules, Stack<String> wiringFileStack, Conf runtimeConf,
ArtifactRepository repository, SourceLocations artifactSourceLocations) throws Exception {
Conf modulesConf = runtimeConf.getRequiredChild("modules");
List<Conf> importConfs = modulesConf.getChildren();
for (Conf importConf : importConfs) {
File fileToImport = null;
ModuleSourceType sourceType = null;
String version = "unknown";
String id = null;
if (importConf.getName().equals("file")) {
String path = PropertyResolver.resolveProperties(importConf.getAttribute("path"));
File file = new File(path);
if (file.getName().toLowerCase().endsWith(".xml")) {
// Import a wiring.xml-type file
includeWiring(file, modules, wiringFileStack, repository, artifactSourceLocations);
} else {
id = importConf.getAttribute("id");
fileToImport = new File(path);
sourceType = ModuleSourceType.JAR;
}
} else if (importConf.getName().equals("artifact")) {
id = importConf.getAttribute("id");
String groupId = importConf.getAttribute("groupId");
String artifactId = importConf.getAttribute("artifactId");
String classifier = importConf.getAttribute("classifier", null);
// Version is optional for org.lilyproject artifacts
version = groupId.equals("org.lilyproject") ?
importConf.getAttribute("version", null) : importConf.getAttribute("version");
version = version == null ? LilyRuntime.getVersion() : version;
File sourceLocation = artifactSourceLocations.getSourceLocation(groupId, artifactId);
if (sourceLocation != null) {
fileToImport = sourceLocation.getCanonicalFile();
if (!fileToImport.exists()) {
throw new LilyRTException("Specified artifact source directory does not exist: "
+ fileToImport.getAbsolutePath(), importConf.getLocation());
}
sourceType = ModuleSourceType.SOURCE_DIRECTORY;
} else {
fileToImport = repository.resolve(groupId, artifactId, classifier, version);
sourceType = ModuleSourceType.JAR;
}
} else if (importConf.getName().equals("directory")) {
id = importConf.getAttribute("id");
String dirName = PropertyResolver.resolveProperties(importConf.getAttribute("path"));
// When basePath is specified, the directory specified in dirName will be resolved
// against basePath. Moreover, basePath can contain a list of paths,
// in which case each basePath will be combined with the path. (this is the only
// reason for having basePath as a separate attribute)
// (It would make sense to allow multiple values for path as well, but the
// need hasn't come up)
String basePath = importConf.getAttribute("basePath", null);
if (basePath != null) {
basePath = PropertyResolver.resolveProperties(basePath);
String[] basePaths = basePath.split(File.pathSeparator);
for (String path : basePaths) {
path = path.trim();
if (path.length() > 0) {
String subDirName = new File(new File(path), dirName).getAbsolutePath();
include(subDirName, id, modules, wiringFileStack, repository, artifactSourceLocations);
}
}
} else {
include(dirName, id, modules, wiringFileStack, repository, artifactSourceLocations);
}
} else {
throw new LilyRTException("Unexpected node: " + importConf.getName(), importConf.getLocation());
}
if (fileToImport != null) {
if (!fileToImport.exists()) {
throw new LilyRTException("Import does not exist: " + fileToImport.getAbsolutePath(),
importConf.getLocation());
}
ModuleDefinition moduleDefinition = new ModuleDefinition(id, fileToImport, sourceType);
moduleDefinition.setLocation(importConf.getLocation());
moduleDefinition.setVersion(version);
buildWiring(importConf, moduleDefinition);
modules.add(moduleDefinition);
}
}
}
private static void include(String dirName, String id, List<ModuleDefinition> modules,
Stack<String> wiringFileStack, ArtifactRepository repository,
SourceLocations artifactSourceLocations) throws Exception {
LogFactory.getLog(LilyRuntimeModelBuilder.class).debug("Searching for wiring includes at " + dirName);
File dir = new File(dirName);
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
// order of imports is important, to provide some deterministic behaviour, sort the entries by name
Arrays.sort(files, new FileNameComparator());
for (File file : files) {
if (file.isDirectory()) {
// skip
} else if (file.getName().toLowerCase().endsWith(".jar")) {
String genId = id + "-" + stripJarExt(file.getName());
modules.add(new ModuleDefinition(genId, file, ModuleSourceType.JAR));
} else if (file.getName().toLowerCase().endsWith(".xml")) {
// Import a wiring.xml-type file
includeWiring(file, modules, wiringFileStack, repository, artifactSourceLocations);
}
}
}
}
private static void includeWiring(File file, List<ModuleDefinition> modules, Stack<String> wiringFileStack,
ArtifactRepository repository, SourceLocations artifactSourceLocations) throws Exception {
String key = file.getCanonicalPath();
if (wiringFileStack.contains(key)) {
throw new LilyRTException("Recursive loading of wiring.xml-type file detected: " + key);
}
Conf conf = XmlConfBuilder.build(file);
// go recursive
wiringFileStack.push(key);
buildModules(modules, wiringFileStack, conf, repository, artifactSourceLocations);
wiringFileStack.pop();
}
private static Pattern URL_REF_PATTERN = Pattern.compile("^url\\(([^)]*)\\)$");
private static Pattern MODULE_REF_PATTERN = Pattern.compile("^([^:]*):([^:]*)$");
public static void buildWiring(Conf conf, ModuleDefinition moduleDef) {
List<Conf> children = conf.getChildren();
for (Conf child : children) {
if (child.getName().equals("inject-javaservice")) {
String name = child.getAttribute("name", null);
String service = child.getAttribute("service", null);
if (name == null && service == null) {
throw new LilyRTException("Either name or service attribute should be specified on inject-javaservice", child.getLocation());
}
String ref = child.getAttribute("ref");
String sourceModule;
String sourceName = null;
Matcher matcher = MODULE_REF_PATTERN.matcher(ref);
if (matcher.matches()) {
sourceModule = matcher.group(1);
sourceName = matcher.group(2);
} else {
sourceModule = ref;
}
if (name != null) {
moduleDef.addInject(new JavaServiceInjectByNameDefinition(name, sourceModule, sourceName));
} else {
moduleDef.addInject(new JavaServiceInjectByServiceDefinition(service, sourceModule, sourceName));
}
}
}
}
private static String stripJarExt(String name) {
if (name.endsWith(".jar")) {
return name.substring(0, name.length() - 4);
} else {
return name;
}
}
private static class FileNameComparator implements Comparator<File> {
public int compare(File o1, File o2) {
return o1.getName().compareTo(o2.getName());
}
}
}