/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.osgi.service; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.URI; import java.net.URL; 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 LayeredBundlePathFactory} when "layers" and "add-ons" are configured. * * @author Brian Stansberry (c) 2012 Red Hat Inc. */ public class LayeredBundlePathTest { private static final String PATH = "layeredbundlepath/"; private File reposRoot; private File repoA; private File repoB; @Before public void setUp() throws Exception { 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); } } 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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false, "base"); } @Test public void testSpecifiedBaseLayer() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", false, false, "base"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false, "base"); } @Test public void testSimpleOverlay() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base"), "top"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, 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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false, "top", "base"); } @Test public void testMultipleOverlays() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base"), "top", "mid"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, 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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false, "top", "mid", "base"); } @Test public void testBasePlusAddOns() throws Exception { createRepo("root-a", true, false, Collections.singletonList("base")); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, true, "base"); } @Test public void testSpecifiedBasePlusAddOns() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", true, false, "base"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, true, "base"); } @Test public void testLayersAndAddOns() throws Exception { createRepo("root-a", true, false, Collections.singletonList("base"), "top", "mid"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, true, "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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, true, "top", "mid", "base"); } @Test public void testBaseLayerAndUser() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base")); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false, "base"); } @Test public void testSpecifiedBaseLayerAndUser() throws Exception { // This setup puts "base" in layers.conf createRepo("root-a", false, true, "base"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false, "base"); } @Test public void testSingleRootComplete() throws Exception { createRepo("root-a", true, false, Collections.singletonList("base"), "top", "mid"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, true, "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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, 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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 1, 0, false, "base"); } @Test public void testSecondRepoLowerPrecedence() throws Exception { createRepo("root-a", false, false, Collections.singletonList("base")); createRepo("root-b", false, true); File[] standardPath = { repoA, repoB }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, 2, false, "base"); } @Test public void testExtraneousOverlay() throws Exception { createRepo("root-a", false, false, Arrays.asList("base", "mid"), "top"); File[] standardPath = { repoA }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, 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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, 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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false); // Now add the layers/base dir new File(repoA, "system/layers/base").mkdirs(); bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, true, "base"); } @Test public void testRejectConfWithNoStructure() throws Exception { createRepo("root-a", false, false); writeLayersConf("root-a", "top"); File[] standardPath = { repoA }; try { LayeredBundlePathFactory.resolveLayeredBundlePath(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 { LayeredBundlePathFactory.resolveLayeredBundlePath(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 }; List<File> bundlePath = LayeredBundlePathFactory.resolveLayeredBundlePath(standardPath); validateBundlePath(bundlePath, repoA, 0, -1, false, "base"); } 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) { createUserBundles(rootName); } } private void createLayer(String rootName, String layerName) throws Exception { createBundles("layers/" + layerName, rootName + "/system/layers/" + layerName, layerName); } private void createAddOn(String rootName, String addOnName) throws Exception { createBundles("add-ons/" + addOnName, rootName + "/system/add-ons/" + addOnName, addOnName); } private void createUserBundles(String rootName) throws Exception { createBundles("user", rootName, "user"); } private void createBundles(String sourcePath, String relativeRepoPath, String uniqueName) throws Exception { copyResource(PATH + sourcePath + "/shared/textnota.jar", PATH, "repos/" + relativeRepoPath + "/test/shared/main"); copyResource(PATH + sourcePath + "/unique/textnota.jar", PATH, "repos/" + relativeRepoPath + "/test/" + uniqueName + "/main"); } private void validateBundlePath(List<File> bundlePathList, File repoRoot, int expectedStartPos, int expectedOtherRootPos, boolean expectAddons, String... layers) { final File[] bundlePath = bundlePathList.toArray(new File[bundlePathList.size()]); 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 bundle path length", expectedStartPos + expectedLength, bundlePath.length); } else { Assert.assertTrue("Correct bundle path length", bundlePath.length > expectedStartPos + expectedLength); } Assert.assertEquals(repoRoot, bundlePath[expectedStartPos]); for (int i = 0; i < layers.length; i++) { File layer = new File(repoRoot, "system/layers/" + layers[i]); Assert.assertEquals(layer, bundlePath[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 = bundlePath[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(bundlePath[i], repoRoot); } } else if (expectedOtherRootPos > 0) { for (int i = expectedOtherRootPos; i < bundlePath.length; i++) { validateNotChild(bundlePath[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 File getResource(final String path) throws Exception { return getResourceFile(getClass(), path); } private void copyResource(final String inputResource, final String outputBase, final String outputPath) throws Exception { final File resource = getResource(inputResource); final File outputDirectory = new File(getResource(outputBase), outputPath); if(!resource.exists()) throw new IllegalArgumentException("Resource does not exist"); if (outputDirectory.exists() && outputDirectory.isFile()) throw new IllegalArgumentException("OutputDirectory must be a directory"); if (!outputDirectory.exists()) { if (!outputDirectory.mkdirs()) throw new RuntimeException("Failed to create output directory"); } final File outputFile = new File(outputDirectory, resource.getName()); final InputStream in = new FileInputStream(resource); try { final OutputStream out = new FileOutputStream(outputFile); try { final byte[] b = new byte[8192]; int c; while ((c = in.read(b)) != -1) { out.write(b, 0, c); } out.close(); in.close(); } finally { safeClose(out); } } finally { safeClose(in); } } public static URL getResource(final Class<?> baseClass, final String path) throws Exception { return baseClass.getClassLoader().getResource(path); } public static File getResourceFile(final Class<?> baseClass, final String path) throws Exception { URL url = getResource(baseClass, path); URI uri = url.toURI(); return new File(uri); } private static void safeClose(final Closeable closeable) { if (closeable != null) try { closeable.close(); } catch (IOException e) { // meh } } }