/* * 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()); } } }