/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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.jboss.modules; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * Tests of {@link LocalModuleLoader} when "layers" and "add-ons" are configured. * * @author Brian Stansberry (c) 2012 Red Hat Inc. */ public class LayeredModulePathTest extends AbstractModuleTestCase { private static final String PATH = "test/layeredmodulepath/"; private static final ModuleIdentifier SHARED = ModuleIdentifier.create("test.shared"); private String originalModulePath; private File reposRoot; private File repoA; private File repoB; @Before public void setUp() throws Exception { originalModulePath = System.getProperty("module.path"); reposRoot = new File(getResource(PATH), "repos"); if (!reposRoot.mkdirs() && !reposRoot.isDirectory()) { throw new IllegalStateException("Cannot create reposRoot"); } repoA = new File(reposRoot, "root-a"); if (!repoA.mkdirs() && !repoA.isDirectory()) { throw new IllegalStateException("Cannot create reposA"); } repoB = new File(reposRoot, "root-b"); if (!repoB.mkdirs() && !repoB.isDirectory()) { throw new IllegalStateException("Cannot create reposB"); } } @After public void tearDown() throws Exception { if (reposRoot != null) { cleanFile(reposRoot); } if (originalModulePath != null) { System.setProperty("module.path", originalModulePath); } else { System.clearProperty("module.path"); } } private void cleanFile(File file) { File[] children = file.listFiles(); if (children != null) { for (File child : children) { cleanFile(child); } } if (!file.delete() && file.exists()) { file.deleteOnExit(); } } @Test public void testBaseLayer() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base")); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "base"); validateModuleLoading(standardPath, false, false, false, "base"); } @Test public void testSpecifiedBaseLayer() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", false, false, "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "base"); validateModuleLoading(standardPath, false, false, false, "base"); } @Test public void testSimpleOverlay() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base"), "top"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "top", "base"); validateModuleLoading(standardPath, false, false, false, "top", "base"); } @Test public void testSpecifiedBaseLayerWithSimpleOverlay() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", false, false, "top", "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "top", "base"); validateModuleLoading(standardPath, false, false, false, "top", "base"); } @Test public void testMultipleOverlays() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base"), "top", "mid"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "top", "mid", "base"); validateModuleLoading(standardPath, false, false, false, "top", "mid", "base"); } @Test public void testSpecifiedBaseLayerWithMultipleOverlays() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", false, false, "top", "mid", "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "top", "mid", "base"); validateModuleLoading(standardPath, false, false, false, "top", "mid", "base"); } @Test public void testBasePlusAddOns() throws Exception { createRepo("root-a", true, false, Collections.singletonList("base")); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, true, "base"); validateModuleLoading(standardPath, true, false, false, "base"); } @Test public void testSpecifiedBasePlusAddOns() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", true, false, "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, true, "base"); validateModuleLoading(standardPath, true, false, false, "base"); } @Test public void testLayersAndAddOns() throws Exception { createRepo("root-a", true, false, Collections.singletonList("base"), "top", "mid"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base"); validateModuleLoading(standardPath, true, false, false, "top", "mid", "base"); } @Test public void testSpecifiedBaseLayersAndAddOns() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", true, false, "top", "mid", "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base"); validateModuleLoading(standardPath, true, false, false, "top", "mid", "base"); } @Test public void testBaseLayerAndUser() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base")); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "base"); validateModuleLoading(standardPath, false, false, false, "base"); } @Test public void testSpecifiedBaseLayerAndUser() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", false, true, "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "base"); validateModuleLoading(standardPath, false, true, true, "base"); } @Test public void testSingleRootComplete() throws Exception { createRepo("root-a", true, false, Collections.singletonList("base"), "top", "mid"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base"); validateModuleLoading(standardPath, true, false, false, "top", "mid", "base"); } @Test public void testSpecifiedBaseSingleRootComplete() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", true, true, "top", "mid", "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, true, "top", "mid", "base"); validateModuleLoading(standardPath, true, true, true, "top", "mid", "base"); } @Test public void testSecondRepoHigherPrecedence() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base")); createRepo("root-b", false, true); File[] standardPath = { repoB, repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 1, 0, false, "base"); validateModuleLoading(standardPath, false, true, true, "base"); } @Test public void testSecondRepoLowerPrecedence() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base")); createRepo("root-b", false, true); File[] standardPath = { repoA, repoB }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, 2, false, "base"); validateModuleLoading(standardPath, false, true, false, "base"); } @Test public void testExtraneousOverlay() throws Exception { createRepo("root-a", false, false, Arrays.asList("base", "mid"), "top"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "top", "base"); validateModuleLoading(standardPath, false, false, false, "top", "base"); } @Test public void testSpecifiedBaseLayerWithExtraneousOverlay() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", false, false, Arrays.asList("mid"), "top", "base"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "top", "base"); validateModuleLoading(standardPath, false, false, false, "top", "base"); } /** Tests that setting up add-ons has no effect without the layers structure */ @Test public void testLayersRequiredForAddOns() throws Exception { createRepo("root-a", true, false); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false); validateModuleLoading(standardPath, false, false, false); // Now add the layers/base dir new File(repoA, "system/layers/base").mkdirs(); modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, true, "base"); validateModuleLoading(standardPath, true, false, false); } @Test public void testRejectConfWithNoStructure() throws Exception { createRepo("root-a", false, false); writeLayersConf("root-a", "top"); File[] standardPath = { repoA }; try { LayeredModulePathFactory.resolveLayeredModulePath(standardPath); Assert.fail("layers.conf with no layers should fail"); } catch (Exception good) { // good } } @Test public void testRejectConfWithMissingLayer() throws Exception { createRepo("root-a", false, false, Arrays.asList("top", "base")); writeLayersConf("root-a", "top", "mid"); File[] standardPath = { repoA }; try { LayeredModulePathFactory.resolveLayeredModulePath(standardPath); Assert.fail("layers.conf with no layers should fail"); } catch (Exception good) { // good } } @Test public void testEmptyLayersConf() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base")); writeLayersConf("root-a"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); validateModulePath(modulePath, repoA, 0, -1, false, "base"); validateModuleLoading(standardPath, false, false, false, "base"); } @Test public void testLayersOverlayModulePath() throws Exception { createRepo("root-a", false, false, Arrays.asList("top", "base")); writeLayersConf("root-a", "top", "base"); createOverlays(repoA, "top", false, "top1", "top2"); createOverlays(repoA, "base", false, "base1", "base2"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); Assert.assertEquals(7, modulePath.length); Assert.assertEquals(repoA, modulePath[0]); Assert.assertEquals(new File(repoA, "system/layers/top/.overlays/top1"), modulePath[1]); Assert.assertEquals(new File(repoA, "system/layers/top/.overlays/top2"), modulePath[2]); Assert.assertEquals(new File(repoA, "system/layers/top"), modulePath[3]); Assert.assertEquals(new File(repoA, "system/layers/base/.overlays/base1"), modulePath[4]); Assert.assertEquals(new File(repoA, "system/layers/base/.overlays/base2"), modulePath[5]); Assert.assertEquals(new File(repoA, "system/layers/base"), modulePath[6]); } @Test public void testAddOnsOverlayModulePath() throws Exception { createRepo("root-a", true, false, Arrays.asList("base")); writeLayersConf("root-a", "base"); createOverlays(repoA, "a", true, "a"); createOverlays(repoA, "b", true, "b"); File[] standardPath = { repoA }; File[] modulePath = LayeredModulePathFactory.resolveLayeredModulePath(standardPath); Assert.assertEquals(6, modulePath.length); Assert.assertEquals(repoA, modulePath[0]); Assert.assertEquals(new File(repoA, "system/layers/base"), modulePath[1]); // The order of the add-ons is non deterministic Assert.assertEquals(".overlays", modulePath[2].getParentFile().getName()); final String firstOverlay = modulePath[2].getName(); Assert.assertEquals(new File(repoA, "system/add-ons/" + firstOverlay), modulePath[3]); Assert.assertEquals(".overlays", modulePath[4].getParentFile().getName()); final String secondOverlays = modulePath[4].getName(); Assert.assertEquals(new File(repoA, "system/add-ons/" + secondOverlays), modulePath[5]); } private void writeLayersConf(String rootName, String... layers) throws IOException { if (layers != null && layers.length > 0) { StringBuilder sb = new StringBuilder("layers="); for (int i = 0; i < layers.length; i++) { if (i > 0) { sb.append(','); } sb.append(layers[i]); } File repo = "root-a".equals(rootName) ? repoA : repoB; File layersConf = new File(repo, "layers.conf"); layersConf.createNewFile(); FileWriter fw = new FileWriter(layersConf); try { PrintWriter pw = new PrintWriter(fw); pw.println(sb.toString()); pw.close(); } finally { try { fw.close(); } catch (Exception e) { // meh } } } } private void createRepo(String rootName, boolean includeAddons, boolean includeUser, String... layers) throws Exception { List<String> empty = Collections.emptyList(); createRepo(rootName, includeAddons, includeUser, empty, layers); } private void createRepo(String rootName, boolean includeAddons, boolean includeUser, List<String> extraLayers, String... layers) throws Exception { if (layers != null && layers.length > 0) { writeLayersConf(rootName, layers); for (String layer : layers) { createLayer(rootName, layer); } } if (extraLayers != null) { for (String extraLayer : extraLayers) { createLayer(rootName, extraLayer); } } if (includeAddons) { createAddOn(rootName, "a"); createAddOn(rootName, "b"); } if (includeUser) { createUserModules(rootName); } } private void createLayer(String rootName, String layerName) throws Exception { createModules("layers/" + layerName, rootName + "/system/layers/" + layerName, layerName); } private void createAddOn(String rootName, String addOnName) throws Exception { createModules("add-ons/" + addOnName, rootName + "/system/add-ons/" + addOnName, addOnName); } private void createUserModules(String rootName) throws Exception { createModules("user", rootName, "user"); } private void createModules(String sourcePath, String relativeRepoPath, String uniqueName) throws Exception { copyResource(PATH + sourcePath + "/shared/module.xml", PATH, "repos/" + relativeRepoPath + "/test/shared/main"); copyResource(PATH + sourcePath + "/unique/module.xml", PATH, "repos/" + relativeRepoPath + "/test/" + uniqueName + "/main"); } private void validateModulePath(File[] modulePath, File repoRoot, int expectedStartPos, int expectedOtherRootPos, boolean expectAddons, String... layers) { int expectedLength = 1 + layers.length + (expectAddons ? 2 : 0); // Validate positional parameters -- check for bad test writers ;) if (expectedOtherRootPos < 0) { Assert.assertEquals(0, expectedStartPos); // } else if (expectedStartPos == 0) { Assert.assertEquals(expectedLength, expectedOtherRootPos); } if (expectedOtherRootPos < 1) { Assert.assertEquals("Correct module path length", expectedStartPos + expectedLength, modulePath.length); } else { Assert.assertTrue("Correct module path length", modulePath.length > expectedStartPos + expectedLength); } Assert.assertEquals(repoRoot, modulePath[expectedStartPos]); for (int i = 0; i < layers.length; i++) { File layer = new File(repoRoot, "system/layers/" + layers[i]); Assert.assertEquals(layer, modulePath[expectedStartPos + i + 1]); } if (expectAddons) { File addOnBase = new File(repoRoot, "system/add-ons"); Set<String> valid = new HashSet<String>(Arrays.asList("a", "b")); for (int i = 0; i < 2; i++) { File addOn = modulePath[expectedStartPos + layers.length + i + 1]; Assert.assertEquals(addOnBase, addOn.getParentFile()); String addOnName = addOn.getName(); Assert.assertTrue(addOnName, valid.remove(addOnName)); } } if (expectedOtherRootPos == 0) { for (int i = 0; i < expectedStartPos; i++) { validateNotChild(modulePath[i], repoRoot); } } else if (expectedOtherRootPos > 0) { for (int i = expectedOtherRootPos; i < modulePath.length; i++) { validateNotChild(modulePath[i], repoRoot); } } } private void validateNotChild(File file, File repoRoot) { File stop = repoRoot.getParentFile(); File testee = file; while (testee != null && !testee.equals(stop)) { Assert.assertFalse(testee.equals(repoRoot)); testee = testee.getParentFile(); } } private void validateModuleLoading(File[] standardPath, boolean expectAddOns, boolean expectUser, boolean expectUserPrecedence, String... layers) throws ModuleLoadException { // This is nasty, but the alternative is exposing the layers config stuff in the LocalModuleLoader API setUpModulePathProperty(standardPath); ModuleLoader moduleLoader = new LocalModuleLoader(); // Validate the expected path produced the shared module if (expectUser || layers.length > 0 || expectAddOns) { Module shared = moduleLoader.loadModule(SHARED); String sharedProp = shared.getProperty("test.prop"); if (expectUserPrecedence) { Assert.assertEquals("user", sharedProp); } else if (layers.length > 0) { Assert.assertEquals(layers[0], sharedProp); } else if (expectAddOns) { Assert.assertTrue("a".equals(sharedProp) || "b".equals(sharedProp)); } } // Validate the expected unique modules are present Set<String> layersSet = new HashSet<String>(Arrays.asList(layers)); loadModule(moduleLoader, "user", expectUser); loadModule(moduleLoader, "top", layersSet.contains("top")); loadModule(moduleLoader, "mid", layersSet.contains("mid")); loadModule(moduleLoader, "base", layersSet.contains("base")); loadModule(moduleLoader, "a", expectAddOns); loadModule(moduleLoader, "b", expectAddOns); } private void setUpModulePathProperty(File[] standardPath) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < standardPath.length; i++) { if (i > 0) { sb.append(File.pathSeparatorChar); } sb.append(standardPath[i].getAbsolutePath()); } System.setProperty("module.path", sb.toString()); } private void loadModule(ModuleLoader moduleLoader, String moduleName, boolean expectAvailable) { ModuleIdentifier id = ModuleIdentifier.create("test." + moduleName); try { Module module = moduleLoader.loadModule(id); if (!expectAvailable) { Assert.fail("test." + moduleName + " should not be loadable"); } String prop = module.getProperty("test.prop"); Assert.assertEquals(moduleName, prop); } catch (ModuleLoadException e) { if (expectAvailable) { Assert.fail(e.getMessage()); } } } private void createOverlays(final File root, final String name, boolean addOn, String... overlays) throws IOException { final File system = new File(root, "system"); final File layers = addOn ? new File(system, "add-ons") : new File(system, "layers"); final File repo = new File(layers, name); final File overlaysRoot = new File(repo, ".overlays"); overlaysRoot.mkdir(); final File overlaysConfig = new File(overlaysRoot, ".overlays"); final OutputStream os = new FileOutputStream(overlaysConfig); try { for (final String overlay : overlays) { os.write(overlay.getBytes()); os.write('\n'); } } finally { if (os != null) try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } }