/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt 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.test.patching;
import static java.lang.String.format;
import static org.jboss.as.patching.Constants.BASE;
import static org.jboss.as.patching.Constants.LAYERS;
import static org.jboss.as.patching.Constants.MODULES;
import static org.jboss.as.patching.Constants.OVERLAYS;
import static org.jboss.as.patching.Constants.SYSTEM;
import static org.jboss.as.patching.IoUtils.newFile;
import static org.jboss.as.patching.IoUtils.safeClose;
import static org.jboss.as.patching.logging.PatchLogger.ROOT_LOGGER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import org.jboss.as.patching.Constants;
import org.jboss.as.patching.HashUtils;
import org.jboss.as.patching.IoUtils;
import org.jboss.as.patching.ZipUtils;
import org.jboss.as.patching.metadata.BundledPatch;
import org.jboss.as.patching.metadata.ContentModification;
import org.jboss.as.patching.metadata.MiscContentItem;
import org.jboss.as.patching.metadata.ModificationType;
import org.jboss.as.patching.metadata.Patch;
import org.jboss.as.patching.metadata.PatchBundleXml;
import org.jboss.as.patching.metadata.PatchXml;
import org.jboss.as.test.patching.util.module.Module;
import org.jboss.dmr.ModelNode;
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import com.google.common.base.Joiner;
/**
* @author Jan Martiska, Jeff Mesnil
*/
public class PatchingTestUtil {
private static final Logger logger = Logger.getLogger(PatchingTestUtil.class);
private static final boolean isWindows = File.separatorChar == '\\';
public static final String CONTAINER = "jboss";
public static final String AS_DISTRIBUTION = System.getProperty("jbossas.dist");
public static final String FILE_SEPARATOR = File.separator;
public static final String RELATIVE_PATCHES_PATH = Joiner.on(FILE_SEPARATOR).join(new String[]{MODULES, SYSTEM, LAYERS, BASE, OVERLAYS});
public static final String PATCHES_PATH = AS_DISTRIBUTION + FILE_SEPARATOR + RELATIVE_PATCHES_PATH;
private static final String RELATIVE_MODULES_PATH = Joiner.on(FILE_SEPARATOR).join(new String[]{MODULES, SYSTEM, LAYERS, BASE});
public static final String MODULES_PATH = AS_DISTRIBUTION + FILE_SEPARATOR + RELATIVE_MODULES_PATH;
public static final File MODULES_DIRECTORY = newFile(new File(AS_DISTRIBUTION), MODULES);
public static final File LAYERS_DIRECTORY = newFile(MODULES_DIRECTORY, SYSTEM, LAYERS);
public static final File BASE_MODULE_DIRECTORY = newFile(LAYERS_DIRECTORY, BASE);
public static final boolean DO_CLEANUP = Boolean.getBoolean("cleanup.tmp");
public static final String AS_VERSION = ProductInfo.PRODUCT_VERSION;
public static final String PRODUCT = ProductInfo.PRODUCT_NAME;
public static String randomString() {
return UUID.randomUUID().toString();
}
public static String randomString(String prefix) {
return prefix + "-" + ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
}
/**
* Converts the contents of a file into a String.
*
* @param filePath
* @return
* @throws java.io.FileNotFoundException
*/
public static String readFile(String filePath) throws IOException {
return new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
}
public static void setFileContent(String filePath, String content) throws IOException {
Files.write(Paths.get(filePath), content.getBytes(StandardCharsets.UTF_8));
}
public static void tree(File dir) {
StringBuilder out = new StringBuilder();
out.append(dir.getParentFile().getAbsolutePath() + "\n");
tree0(out, dir, 1, " ");
logger.trace(out);
ROOT_LOGGER.trace(out.toString());
}
private static void tree0(StringBuilder out, File dir, int indent, String tab) {
StringBuilder shift = new StringBuilder();
for (int i = 0; i < indent; i++) {
shift.append(tab);
}
out.append(shift + dir.getName() + "\n");
for (File child : dir.listFiles()) {
if (child.isDirectory()) {
tree0(out, child, indent + 1, tab);
} else {
out.append(shift + tab + child.getName() + "\n");
}
}
}
public static File touch(File baseDir, String... segments) throws IOException {
File f = baseDir;
for (String segment : segments) {
f = new File(f, segment);
}
f.getParentFile().mkdirs();
f.createNewFile();
return f;
}
public static void dump(File f, String content) throws IOException {
final OutputStream os = new FileOutputStream(f);
try {
os.write(content.getBytes(StandardCharsets.UTF_8));
os.close();
} finally {
IoUtils.safeClose(os);
}
}
public static void dump(File f, byte[] content) throws IOException {
final OutputStream os = new FileOutputStream(f);
try {
os.write(content);
os.close();
} finally {
IoUtils.safeClose(os);
}
}
public static File createModuleXmlFile(File mainDir, String moduleName, String... resources)
throws IOException {
StringBuilder content = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
content.append(
format("<module xmlns=\"urn:jboss:module:1.2\" name=\"%s\" slot=\"main\">\n", moduleName));
content.append(" <resources>\n");
content.append(" <resource-root path=\".\"/>\n");
for (String resource : resources) {
content.append(format(" <resource-root path=\"%s\"/>\n", resource));
}
content.append(" </resources>\n");
content.append("</module>\n");
ROOT_LOGGER.trace(content);
File moduleXMLFile = touch(mainDir, "module.xml");
dump(moduleXMLFile, content.toString());
return moduleXMLFile;
}
public static void createPatchXMLFile(File dir, Patch patch) throws Exception {
File patchXMLfile = new File(dir, "patch.xml");
FileOutputStream fos = new FileOutputStream(patchXMLfile);
try {
PatchXml.marshal(fos, patch);
} finally {
safeClose(fos);
}
}
public static void createPatchBundleXMLFile(File dir, final List<BundledPatch.BundledPatchEntry> patches) throws Exception {
File bundleXMLFile = new File(dir, "patches.xml");
FileOutputStream fos = new FileOutputStream(bundleXMLFile);
try {
PatchBundleXml.marshal(fos, new BundledPatch() {
@Override
public List<BundledPatchEntry> getPatches() {
return patches;
}
});
} finally {
safeClose(fos);
}
}
public static File createZippedPatchFile(File sourceDir, String zipFileName) {
return createZippedPatchFile(sourceDir, zipFileName, null);
}
public static File createZippedPatchFile(File sourceDir, String zipFileName, File targetDir) {
if (targetDir == null) {
targetDir = sourceDir.getParentFile();
}
tree(sourceDir);
File zipFile = new File(targetDir, zipFileName + ".zip");
ZipUtils.zip(sourceDir, zipFile);
return zipFile;
}
public static void assertPatchElements(File baseModuleDir, String[] expectedPatchElements) {
assertPatchElements(baseModuleDir, expectedPatchElements, isWindows); // Skip this on windows
}
public static void assertPatchElements(File baseModuleDir, String[] expectedPatchElements, boolean skipCheck) {
if (skipCheck) {
return;
}
File modulesPatchesDir = new File(baseModuleDir, ".overlays");
if (!modulesPatchesDir.exists()) {
assertNull("Overlay directory does not exist, but it should", expectedPatchElements);
return;
}
final List<File> patchDirs = Arrays.asList(modulesPatchesDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
}
}));
if (expectedPatchElements == null) {
assertTrue("Overlays directory should contain no directories, but contains: " + patchDirs.toString(), patchDirs.isEmpty());
} else {
final List<String> ids = Arrays.asList(expectedPatchElements);
assertEquals("Overlays directory should contain " + expectedPatchElements.length + " patches",
expectedPatchElements.length, patchDirs.size());
for (File f : patchDirs) {
assertTrue("Unexpected patch in .overlays directory: " + f.getName(), ids.contains(f.getName()));
}
}
}
public static void resetInstallationState(final File home, final File... layerDirs) {
resetPatchStreams(home);
for (final File root : layerDirs) {
final File overlays = new File(root, Constants.OVERLAYS);
IoUtils.recursiveDelete(overlays);
}
}
protected static void resetPatchStreams(final File home) {
IoUtils.recursiveDelete(new File(home, Constants.INSTALLATION));
}
static ContentModification updateModulesJar(final File installation, final File patchDir) throws IOException {
final String fileName = "jboss-modules.jar";
final File source = new File(installation, fileName);
final File misc = new File(patchDir, "misc");
misc.mkdirs();
final File target = new File(misc, fileName);
updateJar(source, target);
final byte[] sourceHash = HashUtils.hashFile(source);
final byte[] targetHash = HashUtils.hashFile(target);
assert !Arrays.equals(sourceHash, targetHash);
final MiscContentItem item = new MiscContentItem(fileName, new String[0], targetHash, false, false);
return new ContentModification(item, sourceHash, ModificationType.MODIFY);
}
static void updateJar(final File source, final File target) throws IOException {
final JavaArchive archive = ShrinkWrap.createFromZipFile(JavaArchive.class, source);
archive.add(new StringAsset("test " + randomString()), "testFile");
archive.as(ZipExporter.class).exportTo(target);
}
public static boolean isOneOffPatchContainedInHistory(List<ModelNode> patchingHistory, String patchID) {
boolean found = false;
for (ModelNode node : patchingHistory) {
if (node.get("patch-id").asString().equals(patchID)) { found = true; }
}
return found;
}
public static ResourceItem createVersionItem(final String targetVersion) {
final Asset newManifest = new Asset() {
@Override
public InputStream openStream() {
return new ByteArrayInputStream(ProductInfo.createVersionString(targetVersion).getBytes(StandardCharsets.UTF_8));
}
};
final JavaArchive versionModuleJar = ShrinkWrap.create(JavaArchive.class);
if (!ProductInfo.isProduct) {
versionModuleJar.addPackage("org.jboss.as.version");
}
versionModuleJar.addAsManifestResource(newManifest, "MANIFEST.MF");
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
versionModuleJar.as(ZipExporter.class).exportTo(baos);
return new ResourceItem("as-version.jar", baos.toByteArray());
}
public static Module createVersionModule(final String targetVersion) {
final ResourceItem item = createVersionItem(targetVersion);
return new Module.Builder(ProductInfo.getVersionModule())
.slot(ProductInfo.getVersionModuleSlot())
.resourceRoot(item)
.property("jboss.api", "private")
.dependency("org.jboss.logging")
.dependency("org.jboss.modules")
.build();
}
}