You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ds...@apache.org on 2017/04/21 23:42:14 UTC
[18/51] [abbrv] geode git commit: GEODE-2686: Remove JarClassLoader
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/main/java/org/apache/geode/internal/JarDeployer.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/JarDeployer.java b/geode-core/src/main/java/org/apache/geode/internal/JarDeployer.java
index 18d4b42..ad5c435 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/JarDeployer.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/JarDeployer.java
@@ -14,44 +14,60 @@
*/
package org.apache.geode.internal;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.geode.GemFireException;
+import org.apache.geode.GemFireIOException;
import org.apache.geode.SystemFailure;
import org.apache.geode.internal.logging.LogService;
import org.apache.logging.log4j.Logger;
import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
-import java.io.InputStream;
import java.io.OutputStream;
-import java.io.RandomAccessFile;
import java.io.Serializable;
-import java.nio.channels.FileLock;
+import java.net.URL;
+import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
public class JarDeployer implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger logger = LogService.getLogger();
- public static final String JAR_PREFIX = "vf.gf#";
- public static final String JAR_PREFIX_FOR_REGEX = "^vf\\.gf#";
+ public static final String JAR_PREFIX = "";
+ public static final String JAR_PREFIX_FOR_REGEX = "";
private static final Lock lock = new ReentrantLock();
+ private final Map<String, DeployedJar> deployedJars = new ConcurrentHashMap<>();
+
+
// Split a versioned filename into its name and version
public static final Pattern versionedPattern =
- Pattern.compile(JAR_PREFIX_FOR_REGEX + "(.*)#(\\d++)$");
+ Pattern.compile(JAR_PREFIX_FOR_REGEX + "(.*)\\.v(\\d++).jar$");
private final File deployDirectory;
@@ -63,262 +79,36 @@ public class JarDeployer implements Serializable {
this.deployDirectory = deployDirectory;
}
- /**
- * Re-deploy all previously deployed JAR files.
- */
- public void loadPreviouslyDeployedJars() {
- List<JarClassLoader> jarClassLoaders = new ArrayList<JarClassLoader>();
-
- lock.lock();
- try {
- try {
- verifyWritableDeployDirectory();
- final Set<String> jarNames = findDistinctDeployedJars();
- if (!jarNames.isEmpty()) {
- for (String jarName : jarNames) {
- final File[] jarFiles = findSortedOldVersionsOfJar(jarName);
-
- // It's possible the JARs were deleted by another process
- if (jarFiles.length != 0) {
- JarClassLoader jarClassLoader = findJarClassLoader(jarName);
-
- try {
- final byte[] jarBytes = getJarContent(jarFiles[0]);
- if (!JarClassLoader.isValidJarContent(jarBytes)) {
- logger.warn("Invalid JAR file found and deleted: {}",
- jarFiles[0].getAbsolutePath());
- jarFiles[0].delete();
- } else {
- // Test to see if the exact same file is already in use
- if (jarClassLoader == null
- || !jarClassLoader.getFileName().equals(jarFiles[0].getName())) {
- jarClassLoader = new JarClassLoader(jarFiles[0], jarName, jarBytes);
- ClassPathLoader.getLatest().addOrReplaceAndSetLatest(jarClassLoader);
- jarClassLoaders.add(jarClassLoader);
- }
- }
- } catch (IOException ioex) {
- // Another process deleted the file so don't bother doing anything else with it
- if (logger.isDebugEnabled()) {
- logger.debug("Failed attempt to use JAR to create JarClassLoader for: {}",
- jarName);
- }
- }
-
- // Remove any old left-behind versions of this JAR file
- for (File jarFile : jarFiles) {
- if (jarFile.exists() && (jarClassLoader == null
- || !jarClassLoader.getFileName().equals(jarFile.getName()))) {
- attemptFileLockAndDelete(jarFile);
- }
- }
- }
- }
- }
-
- for (JarClassLoader jarClassLoader : jarClassLoaders) {
- jarClassLoader.loadClassesAndRegisterFunctions();
- }
- } catch (VirtualMachineError e) {
- SystemFailure.initiateFailure(e);
- throw e;
- } catch (Throwable th) {
- SystemFailure.checkFailure();
- logger.error("Error when attempting to deploy JAR files on load.", th);
- }
- } finally {
- lock.unlock();
- }
+ public File getDeployDirectory() {
+ return this.deployDirectory;
}
- /**
- * Deploy the given JAR files.
- *
- * @param jarNames Array of names of the JAR files to deploy.
- * @param jarBytes Array of contents of the JAR files to deploy.
- * @return An array of newly created JAR class loaders. Entries will be null for an JARs that were
- * already deployed.
- * @throws IOException When there's an error saving the JAR file to disk
- */
- public JarClassLoader[] deploy(final String jarNames[], final byte[][] jarBytes)
- throws IOException, ClassNotFoundException {
- JarClassLoader[] jarClassLoaders = new JarClassLoader[jarNames.length];
- verifyWritableDeployDirectory();
-
+ public DeployedJar deployWithoutRegistering(final String jarName, final byte[] jarBytes)
+ throws IOException {
lock.lock();
+
try {
- for (int i = 0; i < jarNames.length; i++) {
- if (!JarClassLoader.isValidJarContent(jarBytes[i])) {
- throw new IllegalArgumentException(
- "File does not contain valid JAR content: " + jarNames[i]);
- }
- }
+ verifyWritableDeployDirectory();
- for (int i = 0; i < jarNames.length; i++) {
- jarClassLoaders[i] = deployWithoutRegistering(jarNames[i], jarBytes[i]);
- }
+ File newVersionedJarFile = getNextVersionedJarFile(jarName);
+ writeJarBytesToFile(newVersionedJarFile, jarBytes);
- for (JarClassLoader jarClassLoader : jarClassLoaders) {
- if (jarClassLoader != null) {
- jarClassLoader.loadClassesAndRegisterFunctions();
- }
- }
+ return new DeployedJar(newVersionedJarFile, jarName, jarBytes);
} finally {
lock.unlock();
}
- return jarClassLoaders;
}
- /**
- * Deploy the given JAR file without registering functions.
- *
- * @param jarName Name of the JAR file to deploy.
- * @param jarBytes Contents of the JAR file to deploy.
- * @return The newly created JarClassLoader or null if the JAR was already deployed
- * @throws IOException When there's an error saving the JAR file to disk
- */
- private JarClassLoader deployWithoutRegistering(final String jarName, final byte[] jarBytes)
- throws IOException {
- JarClassLoader oldJarClassLoader = findJarClassLoader(jarName);
-
- final boolean isDebugEnabled = logger.isDebugEnabled();
- if (isDebugEnabled) {
- logger.debug("Deploying {}: {}", jarName, (oldJarClassLoader == null ? ": not yet deployed"
- : ": already deployed as " + oldJarClassLoader.getFileCanonicalPath()));
- }
-
- // Test to see if the exact same file is being deployed
- if (oldJarClassLoader != null && oldJarClassLoader.hasSameContent(jarBytes)) {
- return null;
- }
-
- JarClassLoader newJarClassLoader = null;
-
- do {
- File[] oldJarFiles = findSortedOldVersionsOfJar(jarName);
-
- try {
- // If this is the first version of this JAR file we've seen ...
- if (oldJarFiles.length == 0) {
- if (isDebugEnabled) {
- logger.debug("There were no pre-existing versions for JAR: {}", jarName);
- }
- File nextVersionJarFile = getNextVersionJarFile(jarName);
- if (writeJarBytesToFile(nextVersionJarFile, jarBytes)) {
- newJarClassLoader = new JarClassLoader(nextVersionJarFile, jarName, jarBytes);
- if (isDebugEnabled) {
- logger.debug("Successfully created initial JarClassLoader at file: {}",
- nextVersionJarFile.getAbsolutePath());
- }
- } else {
- if (isDebugEnabled) {
- logger.debug("Unable to write contents for first version of JAR to file: {}",
- nextVersionJarFile.getAbsolutePath());
- }
- }
-
- } else {
- // Most recent is at the beginning of the list, see if this JAR matches what's
- // already on disk.
- if (doesFileMatchBytes(oldJarFiles[0], jarBytes)) {
- if (isDebugEnabled) {
- logger.debug("A version on disk was an exact match for the JAR being deployed: {}",
- oldJarFiles[0].getAbsolutePath());
- }
- newJarClassLoader = new JarClassLoader(oldJarFiles[0], jarName, jarBytes);
- if (isDebugEnabled) {
- logger.debug("Successfully reused JAR to create JarClassLoader from file: {}",
- oldJarFiles[0].getAbsolutePath());
- }
- } else {
- // This JAR isn't on disk
- if (isDebugEnabled) {
- logger.debug("Need to create a new version for JAR: {}", jarName);
- }
- File nextVersionJarFile = getNextVersionJarFile(oldJarFiles[0].getName());
- if (writeJarBytesToFile(nextVersionJarFile, jarBytes)) {
- newJarClassLoader = new JarClassLoader(nextVersionJarFile, jarName, jarBytes);
- if (isDebugEnabled) {
- logger.debug("Successfully created next JarClassLoader at file: {}",
- nextVersionJarFile.getAbsolutePath());
- }
- } else {
- if (isDebugEnabled) {
- logger.debug("Unable to write contents for next version of JAR to file: {}",
- nextVersionJarFile.getAbsolutePath());
- }
- }
- }
- }
- } catch (IOException ioex) {
- // Another process deleted the file before we could get to it, just start again
- logger.info("Failed attempt to use JAR to create JarClassLoader for: {} : {}", jarName,
- ioex.getMessage());
- }
-
- if (isDebugEnabled) {
- if (newJarClassLoader == null) {
- logger.debug("Unable to determine a JAR file location, will loop and try again: {}",
- jarName);
- } else {
- logger.debug("Exiting loop for JarClassLoader creation using file: {}",
- newJarClassLoader.getFileName());
- }
- }
- } while (newJarClassLoader == null);
-
- ClassPathLoader.getLatest().addOrReplaceAndSetLatest(newJarClassLoader);
-
- // Remove the JAR file that was undeployed as part of this redeploy
- if (oldJarClassLoader != null) {
- attemptFileLockAndDelete(new File(this.deployDirectory, oldJarClassLoader.getFileName()));
- }
-
- return newJarClassLoader;
- }
/**
- * Undeploy the given JAR file.
+ * Get a list of all currently deployed jars.
*
- * @param jarName The name of the JAR file to undeploy
- * @return The path to the location on disk where the JAR file had been deployed
- * @throws IOException If there's a problem deleting the file
+ * @return The list of DeployedJars
*/
- public String undeploy(final String jarName) throws IOException {
- JarClassLoader jarClassLoader = null;
- verifyWritableDeployDirectory();
-
- lock.lock();
- try {
- jarClassLoader = findJarClassLoader(jarName);
- if (jarClassLoader == null) {
- throw new IllegalArgumentException("JAR not deployed");
- }
-
- ClassPathLoader.getLatest().removeAndSetLatest(jarClassLoader);
- attemptFileLockAndDelete(new File(this.deployDirectory, jarClassLoader.getFileName()));
- return jarClassLoader.getFileCanonicalPath();
- } finally {
- lock.unlock();
- }
+ public List<DeployedJar> findDeployedJars() {
+ return getDeployedJars().values().stream().collect(toList());
}
- /**
- * Get a list of all currently deployed JarClassLoaders.
- *
- * @return The list of JarClassLoaders
- */
- public List<JarClassLoader> findJarClassLoaders() {
- List<JarClassLoader> returnList = new ArrayList<JarClassLoader>();
- Collection<ClassLoader> classLoaders = ClassPathLoader.getLatest().getClassLoaders();
- for (ClassLoader classLoader : classLoaders) {
- if (classLoader instanceof JarClassLoader) {
- returnList.add((JarClassLoader) classLoader);
- }
- }
-
- return returnList;
- }
/**
* Suspend all deploy and undeploy operations. This is done by acquiring and holding the lock
@@ -339,26 +129,19 @@ public class JarDeployer implements Serializable {
lock.unlock();
}
- /**
- * Figure out the next version of a JAR file
- *
- * @param latestVersionedJarName The previous most recent version of the JAR file or original name
- * if there wasn't one
- * @return The file that represents the next version
- */
- protected File getNextVersionJarFile(final String latestVersionedJarName) {
- String newFileName;
- final Matcher matcher = versionedPattern.matcher(latestVersionedJarName);
- if (matcher.find()) {
- newFileName = JAR_PREFIX + matcher.group(1) + "#" + (Integer.parseInt(matcher.group(2)) + 1);
+ protected File getNextVersionedJarFile(String unversionedJarName) {
+ File[] oldVersions = findSortedOldVersionsOfJar(unversionedJarName);
+
+ String nextVersionedJarName;
+ if (oldVersions == null || oldVersions.length == 0) {
+ nextVersionedJarName = removeJarExtension(unversionedJarName) + ".v1.jar";
} else {
- newFileName = JAR_PREFIX + latestVersionedJarName + "#1";
+ String latestVersionedJarName = oldVersions[0].getName();
+ int nextVersion = extractVersionFromFilename(latestVersionedJarName) + 1;
+ nextVersionedJarName = removeJarExtension(unversionedJarName) + ".v" + nextVersion + ".jar";
}
- if (logger.isDebugEnabled()) {
- logger.debug("Next version file name will be: {}", newFileName);
- }
- return new File(this.deployDirectory, newFileName);
+ return new File(deployDirectory, nextVersionedJarName);
}
/**
@@ -370,27 +153,18 @@ public class JarDeployer implements Serializable {
* @param jarBytes Contents of the JAR file to deploy.
* @return True if the file was successfully written, false otherwise
*/
- private boolean writeJarBytesToFile(final File file, final byte[] jarBytes) {
+ private boolean writeJarBytesToFile(final File file, final byte[] jarBytes) throws IOException {
final boolean isDebugEnabled = logger.isDebugEnabled();
- try {
- if (file.createNewFile()) {
- if (isDebugEnabled) {
- logger.debug("Successfully created new JAR file: {}", file.getAbsolutePath());
- }
- final OutputStream outStream = new FileOutputStream(file);
- outStream.write(jarBytes);
- outStream.close();
- return true;
- }
- return doesFileMatchBytes(file, jarBytes);
-
- } catch (IOException ioex) {
- // Another VM clobbered what was happening here, try again
+ if (file.createNewFile()) {
if (isDebugEnabled) {
- logger.debug("IOException while trying to write JAR content to file: {}", ioex);
+ logger.debug("Successfully created new JAR file: {}", file.getAbsolutePath());
}
- return false;
+ final OutputStream outStream = new FileOutputStream(file);
+ outStream.write(jarBytes);
+ outStream.close();
+ return true;
}
+ return doesFileMatchBytes(file, jarBytes);
}
/**
@@ -457,86 +231,22 @@ public class JarDeployer implements Serializable {
return true;
}
- private void attemptFileLockAndDelete(final File file) throws IOException {
- final String absolutePath = file.getAbsolutePath();
- FileOutputStream fileOutputStream = new FileOutputStream(file, true);
- final boolean isDebugEnabled = logger.isDebugEnabled();
- try {
- FileLock fileLock = null;
- try {
- fileLock = fileOutputStream.getChannel().tryLock();
-
- if (fileLock != null) {
- if (isDebugEnabled) {
- logger.debug("Tried and acquired exclusive lock for file: {}, w/ channel {}",
- absolutePath, fileLock.channel());
- }
-
- if (file.delete()) {
- if (isDebugEnabled) {
- logger.debug("Deleted file with name: {}", absolutePath);
- }
- } else {
- if (isDebugEnabled) {
- logger.debug("Could not delete file, will truncate instead and delete on exit: {}",
- absolutePath);
- }
- file.deleteOnExit();
-
- RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
- try {
- randomAccessFile.setLength(0);
- } finally {
- try {
- randomAccessFile.close();
- } catch (IOException ioex) {
- logger.error("Could not close file when attempting to set zero length", ioex);
- }
- }
- }
- } else {
- if (isDebugEnabled) {
- logger.debug("Will not delete file since exclusive lock unavailable: {}", absolutePath);
- }
- }
-
- } finally {
- if (fileLock != null) {
- try {
- fileLock.release();
- fileLock.channel().close();
- if (isDebugEnabled) {
- logger.debug("Released file lock for file: {}, w/ channel: {}", absolutePath,
- fileLock.channel());
- }
- } catch (IOException ioex) {
- logger.error("Could not close channel on JAR lock file", ioex);
- }
- }
- }
- } finally {
- try {
- fileOutputStream.close();
- } catch (IOException ioex) {
- logger.error("Could not close output stream on JAR file", ioex);
- }
- }
- }
-
/**
* Find the version number that's embedded in the name of this file
*
- * @param file File to get the version number from
+ * @param filename Filename to get the version number from
* @return The version number embedded in the filename
*/
- int extractVersionFromFilename(final File file) {
- final Matcher matcher = versionedPattern.matcher(file.getName());
- matcher.find();
- return Integer.parseInt(matcher.group(2));
+ public static int extractVersionFromFilename(final String filename) {
+ final Matcher matcher = versionedPattern.matcher(filename);
+ if (matcher.find()) {
+ return Integer.parseInt(matcher.group(2));
+ } else {
+ return 0;
+ }
}
protected Set<String> findDistinctDeployedJars() {
-
// Find all deployed JAR files
final File[] oldFiles = this.deployDirectory.listFiles(new FilenameFilter() {
@Override
@@ -559,41 +269,32 @@ public class JarDeployer implements Serializable {
* Find all versions of the JAR file that are currently on disk and return them sorted from newest
* (highest version) to oldest
*
- * @param jarFilename Name of the JAR file that we want old versions of
+ * @param unversionedJarName Name of the JAR file that we want old versions of
* @return Sorted array of files that are older versions of the given JAR
*/
- protected File[] findSortedOldVersionsOfJar(final String jarFilename) {
+ protected File[] findSortedOldVersionsOfJar(final String unversionedJarName) {
// Find all matching files
- final Pattern pattern = Pattern.compile(JAR_PREFIX_FOR_REGEX + jarFilename + "#\\d++$");
- final File[] oldJarFiles = this.deployDirectory.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(final File file, final String name) {
- return (pattern.matcher(name).matches());
- }
- });
+ final Pattern pattern = Pattern.compile(
+ JAR_PREFIX_FOR_REGEX + removeJarExtension(unversionedJarName) + "\\.v\\d++\\.jar$");
+ final File[] oldJarFiles =
+ this.deployDirectory.listFiles((file, name) -> (pattern.matcher(name).matches()));
// Sort them in order from newest (highest version) to oldest
- Arrays.sort(oldJarFiles, new Comparator<File>() {
- @Override
- public int compare(final File file1, final File file2) {
- int file1Version = extractVersionFromFilename(file1);
- int file2Version = extractVersionFromFilename(file2);
- return file2Version - file1Version;
- }
+ Arrays.sort(oldJarFiles, (file1, file2) -> {
+ int file1Version = extractVersionFromFilename(file1.getName());
+ int file2Version = extractVersionFromFilename(file2.getName());
+ return file2Version - file1Version;
});
return oldJarFiles;
}
- private JarClassLoader findJarClassLoader(final String jarName) {
- Collection<ClassLoader> classLoaders = ClassPathLoader.getLatest().getClassLoaders();
- for (ClassLoader classLoader : classLoaders) {
- if (classLoader instanceof JarClassLoader
- && ((JarClassLoader) classLoader).getJarName().equals(jarName)) {
- return (JarClassLoader) classLoader;
- }
+ protected String removeJarExtension(String jarName) {
+ if (jarName != null && jarName.endsWith(".jar")) {
+ return jarName.replaceAll("\\.jar$", "");
+ } else {
+ return jarName;
}
- return null;
}
/**
@@ -601,7 +302,7 @@ public class JarDeployer implements Serializable {
*
* @throws IOException If the directory isn't writable
*/
- private void verifyWritableDeployDirectory() throws IOException {
+ public void verifyWritableDeployDirectory() throws IOException {
Exception exception = null;
int tryCount = 0;
do {
@@ -627,20 +328,246 @@ public class JarDeployer implements Serializable {
"Unable to write to deploy directory: " + this.deployDirectory.getCanonicalPath());
}
- private byte[] getJarContent(File jarFile) throws IOException {
- InputStream inputStream = new FileInputStream(jarFile);
+ final Pattern oldNamingPattern = Pattern.compile("^vf\\.gf#(.*)\\.jar#(\\d+)$");
+
+ /*
+ * In Geode 1.1.0, the deployed version of 'myjar.jar' would be named 'vf.gf#myjar.jar#1'. Now it
+ * is be named 'myjar.v1.jar'. We need to rename all existing deployed jars to the new convention
+ * if this is the first time starting up with the new naming format.
+ */
+ protected void renameJarsWithOldNamingConvention() throws IOException {
+ Set<File> jarsWithOldNamingConvention = findJarsWithOldNamingConvention();
+
+ if (jarsWithOldNamingConvention.isEmpty()) {
+ return;
+ }
+
+ for (File jar : jarsWithOldNamingConvention) {
+ renameJarWithOldNamingConvention(jar);
+ }
+ }
+
+ protected Set<File> findJarsWithOldNamingConvention() {
+ return Stream.of(this.deployDirectory.listFiles())
+ .filter((File file) -> isOldNamingConvention(file.getName())).collect(toSet());
+ }
+
+ protected boolean isOldNamingConvention(String fileName) {
+ return oldNamingPattern.matcher(fileName).matches();
+ }
+
+ private void renameJarWithOldNamingConvention(File oldJar) throws IOException {
+ Matcher matcher = oldNamingPattern.matcher(oldJar.getName());
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("The given jar " + oldJar.getCanonicalPath()
+ + " does not match the old naming convention");
+ }
+
+ String unversionedJarNameWithoutExtension = matcher.group(1);
+ String jarVersion = matcher.group(2);
+ String newJarName = unversionedJarNameWithoutExtension + ".v" + jarVersion + ".jar";
+
+ File newJar = new File(this.deployDirectory, newJarName);
+ logger.debug("Renaming deployed jar from " + oldJar.getCanonicalPath() + " to "
+ + newJar.getCanonicalPath());
+
+ FileUtils.moveFile(oldJar, newJar);
+ FileUtils.deleteQuietly(oldJar);
+ }
+
+ /**
+ * Re-deploy all previously deployed JAR files.
+ */
+ public void loadPreviouslyDeployedJars() {
+ lock.lock();
+ try {
+ verifyWritableDeployDirectory();
+ renameJarsWithOldNamingConvention();
+
+ final Set<String> jarNames = findDistinctDeployedJars();
+ if (jarNames.isEmpty()) {
+ return;
+ }
+
+ Map<String, DeployedJar> latestVersionOfEachJar = new LinkedHashMap<>();
+
+ for (String jarName : jarNames) {
+ final File[] jarFiles = findSortedOldVersionsOfJar(jarName);
+
+ Optional<File> latestValidDeployedJarOptional =
+ Arrays.stream(jarFiles).filter(Objects::nonNull).filter(jarFile -> {
+ try {
+ return DeployedJar.isValidJarContent(FileUtils.readFileToByteArray(jarFile));
+ } catch (IOException e) {
+ return false;
+ }
+ }).findFirst();
+
+ if (!latestValidDeployedJarOptional.isPresent()) {
+ // No valid version of this jar
+ continue;
+ }
+
+ File latestValidDeployedJar = latestValidDeployedJarOptional.get();
+ latestVersionOfEachJar.put(jarName, new DeployedJar(latestValidDeployedJar, jarName));
+
+ // Remove any old left-behind versions of this JAR file
+ for (File jarFile : jarFiles) {
+ if (!latestValidDeployedJar.equals(jarFile)) {
+ FileUtils.deleteQuietly(jarFile);
+ }
+ }
+ }
+
+ registerNewVersions(latestVersionOfEachJar.values().stream().collect(toList()));
+ // ClassPathLoader.getLatest().deploy(latestVersionOfEachJar.keySet().toArray(),
+ // latestVersionOfEachJar.values().toArray())
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+
+ public URL[] getDeployedJarURLs() {
+ return this.deployedJars.values().stream().map(DeployedJar::getFileURL).toArray(URL[]::new);
+
+ }
+
+ public List<DeployedJar> registerNewVersions(List<DeployedJar> deployedJars)
+ throws ClassNotFoundException {
+ lock.lock();
+ try {
+ for (DeployedJar deployedJar : deployedJars) {
+ if (deployedJar != null) {
+ DeployedJar oldJar = this.deployedJars.put(deployedJar.getJarName(), deployedJar);
+ if (oldJar != null) {
+ oldJar.cleanUp();
+ }
+ }
+ }
+
+ ClassPathLoader.getLatest().rebuildClassLoaderForDeployedJars();
+
+ for (DeployedJar deployedJar : deployedJars) {
+ if (deployedJar != null) {
+ deployedJar.loadClassesAndRegisterFunctions();
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ return deployedJars;
+ }
+
+ /**
+ * Deploy the given JAR files.
+ *
+ * @param jarNames Array of names of the JAR files to deploy.
+ * @param jarBytes Array of contents of the JAR files to deploy.
+ * @return An array of newly created JAR class loaders. Entries will be null for an JARs that were
+ * already deployed.
+ * @throws IOException When there's an error saving the JAR file to disk
+ */
+ public List<DeployedJar> deploy(final String jarNames[], final byte[][] jarBytes)
+ throws IOException, ClassNotFoundException {
+ DeployedJar[] deployedJars = new DeployedJar[jarNames.length];
+
+ for (int i = 0; i < jarNames.length; i++) {
+ if (!DeployedJar.isValidJarContent(jarBytes[i])) {
+ throw new IllegalArgumentException(
+ "File does not contain valid JAR content: " + jarNames[i]);
+ }
+ }
+
+ lock.lock();
+ try {
+ for (int i = 0; i < jarNames.length; i++) {
+ String jarName = jarNames[i];
+ byte[] newJarBytes = jarBytes[i];
+
+ boolean shouldDeployNewVersion = shouldDeployNewVersion(jarName, newJarBytes);
+
+ if (shouldDeployNewVersion) {
+ deployedJars[i] = deployWithoutRegistering(jarName, newJarBytes);
+ } else {
+ deployedJars[i] = null;
+ }
+ }
+
+ return registerNewVersions(Arrays.asList(deployedJars));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private boolean shouldDeployNewVersion(String jarName, byte[] newJarBytes) throws IOException {
+ DeployedJar oldDeployedJar = this.deployedJars.get(jarName);
+
+ if (oldDeployedJar == null) {
+ return true;
+ }
+
+ if (oldDeployedJar.hasSameContentAs(newJarBytes)) {
+ logger.warn("Jar is identical to the latest deployed version: ",
+ oldDeployedJar.getFileCanonicalPath());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public DeployedJar findDeployedJar(String jarName) {
+ return this.deployedJars.get(jarName);
+ }
+
+ public DeployedJar deploy(final String jarName, final byte[] jarBytes)
+ throws IOException, ClassNotFoundException {
+ lock.lock();
+
try {
- final ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
- final byte[] bytes = new byte[4096];
+ List<DeployedJar> deployedJars = deploy(new String[] {jarName}, new byte[][] {jarBytes});
+ if (deployedJars == null || deployedJars.size() == 0) {
+ return null;
+ }
+
+ return deployedJars.get(0);
+ } finally {
+ lock.unlock();
+ }
+
+ }
- int bytesRead;
- while (((bytesRead = inputStream.read(bytes)) != -1)) {
- byteOutStream.write(bytes, 0, bytesRead);
+ public Map<String, DeployedJar> getDeployedJars() {
+ return Collections.unmodifiableMap(this.deployedJars);
+ }
+
+ /**
+ * Undeploy the given JAR file.
+ *
+ * @param jarName The name of the JAR file to undeploy
+ * @return The path to the location on disk where the JAR file had been deployed
+ * @throws IOException If there's a problem deleting the file
+ */
+ public String undeploy(final String jarName) throws IOException {
+ lock.lock();
+
+ try {
+ DeployedJar deployedJar = deployedJars.remove(jarName);
+ if (deployedJar == null) {
+ throw new IllegalArgumentException("JAR not deployed");
}
- return byteOutStream.toByteArray();
+ ClassPathLoader.getLatest().rebuildClassLoaderForDeployedJars();
+
+ deployedJar.cleanUp();
+
+ return deployedJar.getFileCanonicalPath();
} finally {
- inputStream.close();
+ lock.unlock();
}
}
}
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/main/java/org/apache/geode/internal/cache/ClusterConfigurationLoader.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/ClusterConfigurationLoader.java b/geode-core/src/main/java/org/apache/geode/internal/cache/ClusterConfigurationLoader.java
index f904af1..2b627b2 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/ClusterConfigurationLoader.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/ClusterConfigurationLoader.java
@@ -24,9 +24,11 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Properties;
import java.util.Set;
+import org.apache.geode.internal.ClassPathLoader;
import org.apache.logging.log4j.Logger;
import org.apache.geode.UnmodifiableException;
@@ -35,7 +37,7 @@ import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.ClusterConfigurationService;
import org.apache.geode.distributed.internal.tcpserver.TcpClient;
import org.apache.geode.internal.ConfigSource;
-import org.apache.geode.internal.JarClassLoader;
+import org.apache.geode.internal.DeployedJar;
import org.apache.geode.internal.JarDeployer;
import org.apache.geode.internal.admin.remote.DistributionLocatorId;
import org.apache.geode.internal.i18n.LocalizedStrings;
@@ -56,45 +58,38 @@ public class ClusterConfigurationLoader {
*
* @param cache Cache of this member
* @param response {@link ConfigurationResponse} received from the locators
- * @throws IOException
- * @throws ClassNotFoundException
*/
public static void deployJarsReceivedFromClusterConfiguration(Cache cache,
ConfigurationResponse response) throws IOException, ClassNotFoundException {
- if (response == null)
+ if (response == null) {
return;
+ }
String[] jarFileNames = response.getJarNames();
byte[][] jarBytes = response.getJars();
- final JarDeployer jarDeployer = new JarDeployer(
- ((GemFireCacheImpl) cache).getDistributedSystem().getConfig().getDeployWorkingDir());
-
- /******
- * Un-deploy the existing jars, deployed during cache creation, do not delete anything
- */
-
if (jarFileNames != null && jarBytes != null) {
- JarClassLoader[] jarClassLoaders = jarDeployer.deploy(jarFileNames, jarBytes);
- for (int i = 0; i < jarFileNames.length; i++) {
- if (jarClassLoaders[i] != null) {
- logger.info("Deployed " + (jarClassLoaders[i].getFileCanonicalPath()));
- }
- }
+ List<DeployedJar> deployedJars =
+ ClassPathLoader.getLatest().getJarDeployer().deploy(jarFileNames, jarBytes);
+
+ deployedJars.stream().filter(Objects::nonNull)
+ .forEach((jar) -> logger.info("Deployed " + (jar.getFile().getAbsolutePath())));
}
+ // TODO: Jared - Does this need to actually undeploy extra jars like the javadoc says?
}
/***
* Apply the cache-xml cluster configuration on this member
- *
+ *
* @param cache Cache created for this member
* @param response {@link ConfigurationResponse} containing the requested {@link Configuration}
* @param config this member's config.
*/
public static void applyClusterXmlConfiguration(Cache cache, ConfigurationResponse response,
DistributionConfig config) {
- if (response == null || response.getRequestedConfiguration().isEmpty())
+ if (response == null || response.getRequestedConfiguration().isEmpty()) {
return;
+ }
List<String> groups = getGroups(config);
Map<String, Configuration> requestedConfiguration = response.getRequestedConfiguration();
@@ -138,15 +133,16 @@ public class ClusterConfigurationLoader {
/***
* Apply the gemfire properties cluster configuration on this member
- *
+ *
* @param cache Cache created for this member
* @param response {@link ConfigurationResponse} containing the requested {@link Configuration}
* @param config this member's config
*/
public static void applyClusterPropertiesConfiguration(Cache cache,
ConfigurationResponse response, DistributionConfig config) {
- if (response == null || response.getRequestedConfiguration().isEmpty())
+ if (response == null || response.getRequestedConfiguration().isEmpty()) {
return;
+ }
List<String> groups = getGroups(config);
Map<String, Configuration> requestedConfiguration = response.getRequestedConfiguration();
@@ -189,8 +185,6 @@ public class ClusterConfigurationLoader {
*
* @param config this member's configuration.
* @return {@link ConfigurationResponse}
- * @throws ClusterConfigurationNotAvailableException
- * @throws UnknownHostException
*/
public static ConfigurationResponse requestConfigurationFromLocators(DistributionConfig config,
List<String> locatorList)
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java b/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
index 08f916b..fb311e7 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
@@ -1192,7 +1192,7 @@ public class GemFireCacheImpl
listener.cacheCreated(this);
}
- ClassPathLoader.setLatestToDefault();
+ ClassPathLoader.setLatestToDefault(this.system.getConfig().getDeployWorkingDir());
// request and check cluster configuration
ConfigurationResponse configurationResponse = requestSharedConfiguration();
@@ -1239,7 +1239,7 @@ public class GemFireCacheImpl
try {
// Deploy all the jars from the deploy working dir.
- new JarDeployer(this.system.getConfig().getDeployWorkingDir()).loadPreviouslyDeployedJars();
+ ClassPathLoader.getLatest().getJarDeployer().loadPreviouslyDeployedJars();
ClusterConfigurationLoader.applyClusterXmlConfiguration(this, configurationResponse,
system.getConfig());
initializeDeclarativeCache();
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/main/java/org/apache/geode/internal/cache/persistence/BackupManager.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/persistence/BackupManager.java b/geode-core/src/main/java/org/apache/geode/internal/cache/persistence/BackupManager.java
index d052551..deb53cb 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/persistence/BackupManager.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/persistence/BackupManager.java
@@ -22,7 +22,8 @@ import org.apache.geode.distributed.internal.DM;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.MembershipListener;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
-import org.apache.geode.internal.JarClassLoader;
+import org.apache.geode.internal.ClassPathLoader;
+import org.apache.geode.internal.DeployedJar;
import org.apache.geode.internal.JarDeployer;
import org.apache.geode.internal.cache.DiskStoreImpl;
import org.apache.geode.internal.cache.GemFireCacheImpl;
@@ -290,21 +291,20 @@ public class BackupManager implements MembershipListener {
JarDeployer deployer = null;
try {
- deployer = new JarDeployer();
-
/*
* Suspend any user deployed jar file updates during this backup.
*/
+ deployer = ClassPathLoader.getLatest().getJarDeployer();
deployer.suspendAll();
- List<JarClassLoader> jarList = deployer.findJarClassLoaders();
+ List<DeployedJar> jarList = deployer.findDeployedJars();
if (!jarList.isEmpty()) {
File userBackupDir = new File(backupDir, USER_FILES);
if (!userBackupDir.exists()) {
userBackupDir.mkdir();
}
- for (JarClassLoader loader : jarList) {
+ for (DeployedJar loader : jarList) {
File source = new File(loader.getFileCanonicalPath());
File dest = new File(userBackupDir, source.getName());
if (source.isDirectory()) {
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DeployFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DeployFunction.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DeployFunction.java
index 5f1f161..148aa5f 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DeployFunction.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DeployFunction.java
@@ -15,8 +15,10 @@
package org.apache.geode.management.internal.cli.functions;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import org.apache.geode.internal.ClassPathLoader;
import org.apache.logging.log4j.Logger;
import org.apache.geode.SystemFailure;
@@ -27,7 +29,7 @@ import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.InternalEntity;
-import org.apache.geode.internal.JarClassLoader;
+import org.apache.geode.internal.DeployedJar;
import org.apache.geode.internal.JarDeployer;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.logging.LogService;
@@ -62,11 +64,12 @@ public class DeployFunction implements Function, InternalEntity {
}
List<String> deployedList = new ArrayList<String>();
- JarClassLoader[] jarClassLoaders = jarDeployer.deploy(jarFilenames, jarBytes);
+ List<DeployedJar> jarClassLoaders =
+ ClassPathLoader.getLatest().getJarDeployer().deploy(jarFilenames, jarBytes);
for (int i = 0; i < jarFilenames.length; i++) {
deployedList.add(jarFilenames[i]);
- if (jarClassLoaders[i] != null) {
- deployedList.add(jarClassLoaders[i].getFileCanonicalPath());
+ if (jarClassLoaders.get(i) != null) {
+ deployedList.add(jarClassLoaders.get(i).getFileCanonicalPath());
} else {
deployedList.add("Already deployed");
}
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ListDeployedFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ListDeployedFunction.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ListDeployedFunction.java
index 8df24db..3d6a321 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ListDeployedFunction.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/ListDeployedFunction.java
@@ -16,6 +16,8 @@ package org.apache.geode.management.internal.cli.functions;
import java.util.List;
+import org.apache.geode.internal.ClassPathLoader;
+import org.apache.geode.internal.DeployedJar;
import org.apache.logging.log4j.Logger;
import org.apache.geode.SystemFailure;
@@ -26,7 +28,6 @@ import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.InternalEntity;
-import org.apache.geode.internal.JarClassLoader;
import org.apache.geode.internal.JarDeployer;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.logging.LogService;
@@ -45,8 +46,7 @@ public class ListDeployedFunction implements Function, InternalEntity {
try {
Cache cache = CacheFactory.getAnyInstance();
- final JarDeployer jarDeployer = new JarDeployer(
- ((GemFireCacheImpl) cache).getDistributedSystem().getConfig().getDeployWorkingDir());
+ final JarDeployer jarDeployer = ClassPathLoader.getLatest().getJarDeployer();
DistributedMember member = cache.getDistributedSystem().getDistributedMember();
@@ -56,10 +56,10 @@ public class ListDeployedFunction implements Function, InternalEntity {
memberId = member.getName();
}
- final List<JarClassLoader> jarClassLoaders = jarDeployer.findJarClassLoaders();
+ final List<DeployedJar> jarClassLoaders = jarDeployer.findDeployedJars();
final String[] jars = new String[jarClassLoaders.size() * 2];
int index = 0;
- for (JarClassLoader jarClassLoader : jarClassLoaders) {
+ for (DeployedJar jarClassLoader : jarClassLoaders) {
jars[index++] = jarClassLoader.getJarName();
jars[index++] = jarClassLoader.getFileCanonicalPath();
}
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UndeployFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UndeployFunction.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UndeployFunction.java
index 3f05082..14d875e 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UndeployFunction.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/UndeployFunction.java
@@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
+import org.apache.geode.internal.ClassPathLoader;
import org.apache.logging.log4j.Logger;
import org.apache.geode.SystemFailure;
@@ -28,7 +29,7 @@ import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.InternalEntity;
-import org.apache.geode.internal.JarClassLoader;
+import org.apache.geode.internal.DeployedJar;
import org.apache.geode.internal.JarDeployer;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.logging.LogService;
@@ -50,8 +51,7 @@ public class UndeployFunction implements Function, InternalEntity {
final String jarFilenameList = (String) args[0]; // Comma separated
Cache cache = CacheFactory.getAnyInstance();
- final JarDeployer jarDeployer = new JarDeployer(
- ((GemFireCacheImpl) cache).getDistributedSystem().getConfig().getDeployWorkingDir());
+ final JarDeployer jarDeployer = ClassPathLoader.getLatest().getJarDeployer();
DistributedMember member = cache.getDistributedSystem().getDistributedMember();
@@ -63,13 +63,14 @@ public class UndeployFunction implements Function, InternalEntity {
String[] undeployedJars = new String[0];
if (jarFilenameList == null || jarFilenameList.equals("")) {
- final List<JarClassLoader> jarClassLoaders = jarDeployer.findJarClassLoaders();
+ final List<DeployedJar> jarClassLoaders = jarDeployer.findDeployedJars();
undeployedJars = new String[jarClassLoaders.size() * 2];
int index = 0;
- for (JarClassLoader jarClassLoader : jarClassLoaders) {
+ for (DeployedJar jarClassLoader : jarClassLoaders) {
undeployedJars[index++] = jarClassLoader.getJarName();
try {
- undeployedJars[index++] = jarDeployer.undeploy(jarClassLoader.getJarName());
+ undeployedJars[index++] =
+ ClassPathLoader.getLatest().getJarDeployer().undeploy(jarClassLoader.getJarName());
} catch (IllegalArgumentException iaex) {
// It's okay for it to have have been uneployed from this server
undeployedJars[index++] = iaex.getMessage();
@@ -82,7 +83,7 @@ public class UndeployFunction implements Function, InternalEntity {
String jarFilename = jarTokenizer.nextToken().trim();
try {
undeployedList.add(jarFilename);
- undeployedList.add(jarDeployer.undeploy(jarFilename));
+ undeployedList.add(ClassPathLoader.getLatest().getJarDeployer().undeploy(jarFilename));
} catch (IllegalArgumentException iaex) {
// It's okay for it to not have been deployed to this server
undeployedList.add(iaex.getMessage());
http://git-wip-us.apache.org/repos/asf/geode/blob/6fd2d123/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
index c52d575..d30feb6 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
@@ -14,6 +14,8 @@
*/
package org.apache.geode.internal;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.*;
import java.io.BufferedInputStream;
@@ -24,11 +26,20 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
import java.util.Vector;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.ClassGen;
+import org.apache.commons.io.FileUtils;
+import org.apache.geode.cache.execute.Execution;
+import org.apache.geode.cache.execute.FunctionService;
+import org.apache.geode.cache.execute.ResultCollector;
+import org.apache.geode.distributed.DistributedSystem;
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.test.dunit.rules.ServerStarterRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -53,8 +64,9 @@ public class ClassPathLoaderIntegrationTest {
private static final int TEMP_FILE_BYTES_COUNT = 256;
- private volatile File tempFile;
- private volatile File tempFile2;
+ private File tempFile;
+ private File tempFile2;
+ private File extLibsDir;
@Rule
public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
@@ -65,8 +77,9 @@ public class ClassPathLoaderIntegrationTest {
@Before
public void setUp() throws Exception {
System.setProperty(ClassPathLoader.EXCLUDE_TCCL_PROPERTY, "false");
- System.setProperty(ClassPathLoader.EXT_LIB_DIR_PARENT_PROPERTY,
- this.temporaryFolder.getRoot().getAbsolutePath());
+
+ extLibsDir = new File(this.temporaryFolder.getRoot(), "ext");
+ extLibsDir.mkdirs();
this.tempFile = this.temporaryFolder.newFile("tempFile1.tmp");
FileOutputStream fos = new FileOutputStream(this.tempFile);
@@ -77,98 +90,248 @@ public class ClassPathLoaderIntegrationTest {
fos = new FileOutputStream(this.tempFile2);
fos.write(new byte[TEMP_FILE_BYTES_COUNT]);
fos.close();
+
+ System.setProperty("user.dir", temporaryFolder.getRoot().getAbsolutePath());
+ ClassPathLoader.setLatestToDefault(temporaryFolder.getRoot());
}
- /**
- * Verifies that <tt>getResource</tt> works with custom loader from {@link ClassPathLoader}.
- */
+
@Test
- public void testGetResourceWithCustomLoader() throws Exception {
- System.out.println("\nStarting ClassPathLoaderTest#testGetResourceWithCustomLoader");
+ public void testDeployFileAndChange() throws IOException, ClassNotFoundException {
+ String jarName = "JarDeployerIntegrationTest.jar";
- ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false);
- dcl = dcl.addOrReplace(new GeneratingClassLoader());
+ String classAResource = "integration/parent/ClassA.class";
+ String classBResource = "integration/parent/ClassB.class";
- String resourceToGet = "com/nowhere/testGetResourceWithCustomLoader.rsc";
- URL url = dcl.getResource(resourceToGet);
- assertNotNull(url);
+ String classAName = "integration.parent.ClassA";
+ String classBName = "integration.parent.ClassB";
- InputStream is = url != null ? url.openStream() : null;
- assertNotNull(is);
+ byte[] firstJarBytes = createJarWithClass("ClassA");
- int totalBytesRead = 0;
- byte[] input = new byte[128];
+ // First deploy of the JAR file
+ File firstDeployedJarFile =
+ ClassPathLoader.getLatest().getJarDeployer().deploy(jarName, firstJarBytes).getFile();
- BufferedInputStream bis = new BufferedInputStream(is);
- for (int bytesRead = bis.read(input); bytesRead > -1;) {
- totalBytesRead += bytesRead;
- bytesRead = bis.read(input);
- }
- bis.close();
+ assertThat(firstDeployedJarFile).exists().hasBinaryContent(firstJarBytes);
+ assertThat(firstDeployedJarFile.getName()).contains(".v1.").doesNotContain(".v2.");
+
+ assertThatClassCanBeLoaded(classAName);
+ assertThatClassCannotBeLoaded(classBName);
+
+ assertThatResourceCanBeLoaded(classAResource);
+ assertThatResourceCannotBeLoaded(classBResource);
- assertEquals(TEMP_FILE_BYTES_COUNT, totalBytesRead);
+ // Now deploy an updated JAR file and make sure that the next version of the JAR file
+ // was created and the first one is no longer used
+ byte[] secondJarBytes = createJarWithClass("ClassB");
+
+ File secondDeployedJarFile =
+ ClassPathLoader.getLatest().getJarDeployer().deploy(jarName, secondJarBytes).getFile();
+
+ assertThat(secondDeployedJarFile).exists().hasBinaryContent(secondJarBytes);
+ assertThat(secondDeployedJarFile.getName()).contains(".v2.").doesNotContain(".v1.");
+
+ assertThatClassCanBeLoaded(classBName);
+ assertThatClassCannotBeLoaded(classAName);
+
+ assertThatResourceCanBeLoaded(classBResource);
+ assertThatResourceCannotBeLoaded(classAResource);
+
+ // Now undeploy JAR and make sure it gets cleaned up
+ ClassPathLoader.getLatest().getJarDeployer().undeploy(jarName);
+ assertThatClassCannotBeLoaded(classBName);
+ assertThatClassCannotBeLoaded(classAName);
+
+ assertThatResourceCannotBeLoaded(classBResource);
+ assertThatResourceCannotBeLoaded(classAResource);
}
- /**
- * Verifies that <tt>getResources</tt> works with custom loader from {@link ClassPathLoader}.
- */
@Test
- public void testGetResourcesWithCustomLoader() throws Exception {
- System.out.println("\nStarting ClassPathLoaderTest#testGetResourceWithCustomLoader");
+ public void testDeployNoUpdateWhenNoChange() throws IOException, ClassNotFoundException {
+ String jarName = "JarDeployerIntegrationTest.jar";
- ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false);
- dcl = dcl.addOrReplace(new GeneratingClassLoader());
+ // First deploy of the JAR file
+ byte[] jarBytes = new ClassBuilder().createJarFromName("JarDeployerDUnitDNUWNC");
+ DeployedJar jarClassLoader =
+ ClassPathLoader.getLatest().getJarDeployer().deploy(jarName, jarBytes);
+ File deployedJar = new File(jarClassLoader.getFileCanonicalPath());
- String resourceToGet = "com/nowhere/testGetResourceWithCustomLoader.rsc";
- Enumeration<URL> urls = dcl.getResources(resourceToGet);
- assertNotNull(urls);
- assertTrue(urls.hasMoreElements());
+ assertThat(deployedJar).exists();
+ assertThat(deployedJar.getName()).contains(".v1.");
- URL url = urls.nextElement();
- InputStream is = url != null ? url.openStream() : null;
- assertNotNull(is);
+ // Re-deploy of the same JAR should do nothing
+ DeployedJar newJarClassLoader =
+ ClassPathLoader.getLatest().getJarDeployer().deploy(jarName, jarBytes);
+ assertThat(newJarClassLoader).isNull();
+ assertThat(deployedJar).exists();
- int totalBytesRead = 0;
- byte[] input = new byte[128];
+ }
- BufferedInputStream bis = new BufferedInputStream(is);
- for (int bytesRead = bis.read(input); bytesRead > -1;) {
- totalBytesRead += bytesRead;
- bytesRead = bis.read(input);
- }
- bis.close();
+ @Test
+ public void testDeployWithExistingDependentJars() throws Exception {
+ ClassBuilder classBuilder = new ClassBuilder();
+ final File parentJarFile =
+ new File(temporaryFolder.getRoot(), "JarDeployerDUnitAParent.v1.jar");
+ final File usesJarFile = new File(temporaryFolder.getRoot(), "JarDeployerDUnitUses.v1.jar");
+ final File functionJarFile =
+ new File(temporaryFolder.getRoot(), "JarDeployerDUnitFunction.v1.jar");
+
+ // Write out a JAR files.
+ StringBuffer stringBuffer = new StringBuffer();
+ stringBuffer.append("package jddunit.parent;");
+ stringBuffer.append("public class JarDeployerDUnitParent {");
+ stringBuffer.append("public String getValueParent() {");
+ stringBuffer.append("return \"PARENT\";}}");
+
+ byte[] jarBytes = classBuilder.createJarFromClassContent(
+ "jddunit/parent/JarDeployerDUnitParent", stringBuffer.toString());
+ FileOutputStream outStream = new FileOutputStream(parentJarFile);
+ outStream.write(jarBytes);
+ outStream.close();
+
+ stringBuffer = new StringBuffer();
+ stringBuffer.append("package jddunit.uses;");
+ stringBuffer.append("public class JarDeployerDUnitUses {");
+ stringBuffer.append("public String getValueUses() {");
+ stringBuffer.append("return \"USES\";}}");
- assertEquals(TEMP_FILE_BYTES_COUNT, totalBytesRead);
+ jarBytes = classBuilder.createJarFromClassContent("jddunit/uses/JarDeployerDUnitUses",
+ stringBuffer.toString());
+ outStream = new FileOutputStream(usesJarFile);
+ outStream.write(jarBytes);
+ outStream.close();
+
+ stringBuffer = new StringBuffer();
+ stringBuffer.append("package jddunit.function;");
+ stringBuffer.append("import jddunit.parent.JarDeployerDUnitParent;");
+ stringBuffer.append("import jddunit.uses.JarDeployerDUnitUses;");
+ stringBuffer.append("import org.apache.geode.cache.execute.Function;");
+ stringBuffer.append("import org.apache.geode.cache.execute.FunctionContext;");
+ stringBuffer.append(
+ "public class JarDeployerDUnitFunction extends JarDeployerDUnitParent implements Function {");
+ stringBuffer.append("private JarDeployerDUnitUses uses = new JarDeployerDUnitUses();");
+ stringBuffer.append("public boolean hasResult() {return true;}");
+ stringBuffer.append(
+ "public void execute(FunctionContext context) {context.getResultSender().lastResult(getValueParent() + \":\" + uses.getValueUses());}");
+ stringBuffer.append("public String getId() {return \"JarDeployerDUnitFunction\";}");
+ stringBuffer.append("public boolean optimizeForWrite() {return false;}");
+ stringBuffer.append("public boolean isHA() {return false;}}");
+
+ ClassBuilder functionClassBuilder = new ClassBuilder();
+ functionClassBuilder.addToClassPath(parentJarFile.getAbsolutePath());
+ functionClassBuilder.addToClassPath(usesJarFile.getAbsolutePath());
+ jarBytes = functionClassBuilder.createJarFromClassContent(
+ "jddunit/function/JarDeployerDUnitFunction", stringBuffer.toString());
+ outStream = new FileOutputStream(functionJarFile);
+ outStream.write(jarBytes);
+ outStream.close();
+
+ Properties properties = new Properties();
+ properties.setProperty("user.dir", temporaryFolder.getRoot().getAbsolutePath());
+ ServerStarterRule serverStarterRule = new ServerStarterRule();
+ serverStarterRule.startServer();
+
+ GemFireCacheImpl gemFireCache = GemFireCacheImpl.getInstance();
+ DistributedSystem distributedSystem = gemFireCache.getDistributedSystem();
+ Execution execution =
+ FunctionService.onMember(distributedSystem, distributedSystem.getDistributedMember());
+ ResultCollector resultCollector = execution.execute("JarDeployerDUnitFunction");
+ @SuppressWarnings("unchecked")
+ List<String> result = (List<String>) resultCollector.getResult();
+ assertEquals("PARENT:USES", result.get(0));
+
+ serverStarterRule.after();
}
- /**
- * Verifies that <tt>getResourceAsStream</tt> works with custom loader from
- * {@link ClassPathLoader}.
- */
@Test
- public void testGetResourceAsStreamWithCustomLoader() throws Exception {
- System.out.println("\nStarting ClassPathLoaderTest#testGetResourceAsStreamWithCustomLoader");
+ public void deployNewVersionOfFunctionOverOldVersion() throws Exception {
+ File jarVersion1 = createVersionOfJar("Version1", "MyFunction", "MyJar.jar");
+ File jarVersion2 = createVersionOfJar("Version2", "MyFunction", "MyJar.jar");
- ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false);
- dcl = dcl.addOrReplace(new GeneratingClassLoader());
+ Properties properties = new Properties();
+ properties.setProperty("user.dir", temporaryFolder.getRoot().getAbsolutePath());
+ ServerStarterRule serverStarterRule = new ServerStarterRule();
+ serverStarterRule.startServer();
- String resourceToGet = "com/nowhere/testGetResourceAsStreamWithCustomLoader.rsc";
- InputStream is = dcl.getResourceAsStream(resourceToGet);
- assertNotNull(is);
+ GemFireCacheImpl gemFireCache = GemFireCacheImpl.getInstance();
+ DistributedSystem distributedSystem = gemFireCache.getDistributedSystem();
- int totalBytesRead = 0;
- byte[] input = new byte[128];
+ ClassPathLoader.getLatest().getJarDeployer().deploy("MyJar.jar",
+ FileUtils.readFileToByteArray(jarVersion1));
- BufferedInputStream bis = new BufferedInputStream(is);
- for (int bytesRead = bis.read(input); bytesRead > -1;) {
- totalBytesRead += bytesRead;
- bytesRead = bis.read(input);
- }
- bis.close();
+ assertThatClassCanBeLoaded("jddunit.function.MyFunction");
+ Execution execution =
+ FunctionService.onMember(distributedSystem, distributedSystem.getDistributedMember());
+
+ List<String> result = (List<String>) execution.execute("MyFunction").getResult();
+ assertThat(result.get(0)).isEqualTo("Version1");
+
+
+ ClassPathLoader.getLatest().getJarDeployer().deploy("MyJar.jar",
+ FileUtils.readFileToByteArray(jarVersion2));
+ result = (List<String>) execution.execute("MyFunction").getResult();
+ assertThat(result.get(0)).isEqualTo("Version2");
- assertEquals(TEMP_FILE_BYTES_COUNT, totalBytesRead);
+
+ serverStarterRule.after();
}
+
+ private File createVersionOfJar(String version, String functionName, String jarName)
+ throws IOException {
+ String classContents =
+ "package jddunit.function;" + "import org.apache.geode.cache.execute.Function;"
+ + "import org.apache.geode.cache.execute.FunctionContext;" + "public class "
+ + functionName + " implements Function {" + "public boolean hasResult() {return true;}"
+ + "public String getId() {return \"" + functionName + "\";}"
+ + "public void execute(FunctionContext context) {context.getResultSender().lastResult(\""
+ + version + "\");}}";
+
+ File jar = new File(this.temporaryFolder.newFolder(version), jarName);
+ ClassBuilder functionClassBuilder = new ClassBuilder();
+ functionClassBuilder.writeJarFromContent("jddunit/function/" + functionName, classContents,
+ jar);
+
+ return jar;
+ }
+
+ private void assertThatClassCanBeLoaded(String className) throws ClassNotFoundException {
+ assertThat(ClassPathLoader.getLatest().forName(className)).isNotNull();
+ }
+
+ private void assertThatClassCannotBeLoaded(String className) throws ClassNotFoundException {
+ assertThatThrownBy(() -> ClassPathLoader.getLatest().forName(className))
+ .isExactlyInstanceOf(ClassNotFoundException.class);
+ }
+
+ private void assertThatResourceCanBeLoaded(String resourceName) throws IOException {
+ // ClassPathLoader.getResource
+ assertThat(ClassPathLoader.getLatest().getResource(resourceName)).isNotNull();
+
+ // ClassPathLoader.getResources
+ Enumeration<URL> urls = ClassPathLoader.getLatest().getResources(resourceName);
+ assertThat(urls).isNotNull();
+ assertThat(urls.hasMoreElements()).isTrue();
+
+ // ClassPathLoader.getResourceAsStream
+ InputStream is = ClassPathLoader.getLatest().getResourceAsStream(resourceName);
+ assertThat(is).isNotNull();
+ }
+
+ private void assertThatResourceCannotBeLoaded(String resourceName) throws IOException {
+ // ClassPathLoader.getResource
+ assertThat(ClassPathLoader.getLatest().getResource(resourceName)).isNull();
+
+ // ClassPathLoader.getResources
+ Enumeration<URL> urls = ClassPathLoader.getLatest().getResources(resourceName);
+ assertThat(urls.hasMoreElements()).isFalse();
+
+ // ClassPathLoader.getResourceAsStream
+ InputStream is = ClassPathLoader.getLatest().getResourceAsStream(resourceName);
+ assertThat(is).isNull();
+ }
+
+
/**
* Verifies that <tt>getResource</tt> works with TCCL from {@link ClassPathLoader}.
*/
@@ -281,154 +444,6 @@ public class ClassPathLoaderIntegrationTest {
}
}
- /**
- * Verifies that JAR files found in the extlib directory will be correctly added to the
- * {@link ClassPathLoader}.
- */
- @Test
- public void testJarsInExtLib() throws Exception {
- System.out.println("\nStarting ClassPathLoaderTest#testJarsInExtLib");
-
- File EXT_LIB_DIR = ClassPathLoader.defineEXT_LIB_DIR();
- EXT_LIB_DIR.mkdir();
-
- File subdir = new File(EXT_LIB_DIR, "cplju");
- subdir.mkdir();
-
- final ClassBuilder classBuilder = new ClassBuilder();
-
- writeJarBytesToFile(new File(EXT_LIB_DIR, "ClassPathLoaderJUnit1.jar"),
- classBuilder.createJarFromClassContent("com/cpljunit1/ClassPathLoaderJUnit1",
- "package com.cpljunit1; public class ClassPathLoaderJUnit1 {}"));
- writeJarBytesToFile(new File(subdir, "ClassPathLoaderJUnit2.jar"),
- classBuilder.createJarFromClassContent("com/cpljunit2/ClassPathLoaderJUnit2",
- "package com.cpljunit2; public class ClassPathLoaderJUnit2 {}"));
-
- ClassPathLoader classPathLoader = ClassPathLoader.createWithDefaults(false);
- try {
- classPathLoader.forName("com.cpljunit1.ClassPathLoaderJUnit1");
- } catch (ClassNotFoundException cnfex) {
- fail("JAR file not correctly added to Classpath");
- }
-
- try {
- classPathLoader.forName("com.cpljunit2.ClassPathLoaderJUnit2");
- } catch (ClassNotFoundException cnfex) {
- fail("JAR file not correctly added to Classpath");
- }
-
- assertNotNull(classPathLoader.getResource("com/cpljunit2/ClassPathLoaderJUnit2.class"));
-
- Enumeration<URL> urls = classPathLoader.getResources("com/cpljunit1");
- if (!urls.hasMoreElements()) {
- fail("Resources should return one element");
- }
- }
-
- /**
- * Verifies that the 3rd custom loader will get the resource. Parent cannot find it and TCCL is
- * broken. This verifies that all custom loaders are checked and that the custom loaders are all
- * checked before TCCL.
- */
- @Test
- public void testGetResourceAsStreamWithMultipleCustomLoaders() throws Exception {
- System.out
- .println("\nStarting ClassPathLoaderTest#testGetResourceAsStreamWithMultipleCustomLoaders");
-
- // create DCL such that the 3rd loader should find the resource
- // first custom loader becomes parent which won't find anything
- ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false);
- dcl = dcl.addOrReplace(new GeneratingClassLoader());
- dcl = dcl.addOrReplace(new SimpleClassLoader(getClass().getClassLoader()));
- dcl = dcl.addOrReplace(new NullClassLoader());
-
- String resourceToGet = "com/nowhere/testGetResourceAsStreamWithMultipleCustomLoaders.rsc";
-
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- try {
- // set TCCL to throw errors which makes sure we find before checking TCCL
- Thread.currentThread().setContextClassLoader(new BrokenClassLoader());
-
- InputStream is = dcl.getResourceAsStream(resourceToGet);
- assertNotNull(is);
- is.close();
- } finally {
- Thread.currentThread().setContextClassLoader(cl);
- }
- }
-
- /**
- * Verifies that the 3rd custom loader will get the resource. Parent cannot find it and TCCL is
- * broken. This verifies that all custom loaders are checked and that the custom loaders are all
- * checked before TCCL.
- */
- @Test
- public void testGetResourceWithMultipleCustomLoaders() throws Exception {
- System.out.println("\nStarting ClassPathLoaderTest#testGetResourceWithMultipleCustomLoaders");
-
- // create DCL such that the 3rd loader should find the resource
- // first custom loader becomes parent which won't find anything
- ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false);
- dcl = dcl.addOrReplace(new GeneratingClassLoader());
- dcl = dcl.addOrReplace(new SimpleClassLoader(getClass().getClassLoader()));
- dcl = dcl.addOrReplace(new NullClassLoader());
-
- String resourceToGet = "com/nowhere/testGetResourceWithMultipleCustomLoaders.rsc";
-
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- try {
- // set TCCL to throw errors which makes sure we find before checking TCCL
- Thread.currentThread().setContextClassLoader(new BrokenClassLoader());
-
- URL url = dcl.getResource(resourceToGet);
- assertNotNull(url);
- } finally {
- Thread.currentThread().setContextClassLoader(cl);
- }
- }
-
- /**
- * Verifies that the 3rd custom loader will get the resources. Parent cannot find it and TCCL is
- * broken. This verifies that all custom loaders are checked and that the custom loaders are all
- * checked before TCCL.
- */
- @Test
- public void testGetResourcesWithMultipleCustomLoaders() throws Exception {
- System.out.println("\nStarting ClassPathLoaderTest#testGetResourceWithMultipleCustomLoaders");
-
- // create DCL such that the 3rd loader should find the resource
- // first custom loader becomes parent which won't find anything
- ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false);
- dcl = dcl.addOrReplace(new GeneratingClassLoader());
- dcl = dcl.addOrReplace(new GeneratingClassLoader2());
- dcl = dcl.addOrReplace(new SimpleClassLoader(getClass().getClassLoader()));
- dcl = dcl.addOrReplace(new NullClassLoader());
-
- String resourceToGet = "com/nowhere/testGetResourceWithMultipleCustomLoaders.rsc";
-
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- try {
- // set TCCL to throw errors which makes sure we find before checking TCCL
- Thread.currentThread().setContextClassLoader(new BrokenClassLoader());
-
- Enumeration<URL> urls = dcl.getResources(resourceToGet);
- assertNotNull(urls);
- assertTrue(urls.hasMoreElements());
-
- URL url = urls.nextElement();
- assertNotNull(url);
-
- // Should find two with unique URLs
- assertTrue("Did not find all resources.", urls.hasMoreElements());
- URL url2 = urls.nextElement();
- assertNotNull(url2);
- assertTrue("Resource URLs should be unique.", !url.equals(url2));
-
- } finally {
- Thread.currentThread().setContextClassLoader(cl);
- }
- }
-
private void writeJarBytesToFile(File jarFile, byte[] jarBytes) throws IOException {
final OutputStream outStream = new FileOutputStream(jarFile);
outStream.write(jarBytes);
@@ -508,4 +523,11 @@ public class ClassPathLoaderIntegrationTest {
return tempFile2;
}
}
+
+ private byte[] createJarWithClass(String className) throws IOException {
+ String stringBuilder = "package integration.parent;" + "public class " + className + " {}";
+
+ return new ClassBuilder().createJarFromClassContent("integration/parent/" + className,
+ stringBuilder);
+ }
}