This is an automated email from the ASF dual-hosted git repository.
nitiraj pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/branch-2.6 by this push:
new 64cc76b AMBARI-24231 : Adding additional jars in classpath of ambari
views (nitirajrathore) (#2160)
64cc76b is described below
commit 64cc76bfc7c83707f395d8487ea983dc8fddb267
Author: nitirajrathore <[email protected]>
AuthorDate: Fri Aug 31 09:21:59 2018 +0530
AMBARI-24231 : Adding additional jars in classpath of ambari views
(nitirajrathore) (#2160)
* AMBARI-24231 : Adding additional jars in classpath of ambari views
(nitirajrathore)
* AMBARI-24231 : (review comments) Adding additional jars in classpath of
ambari views (nitirajrathore)
---
ambari-server/conf/unix/ambari.properties | 1 +
.../ambari/server/configuration/Configuration.java | 22 ++++++++++++
.../apache/ambari/server/view/ViewExtractor.java | 40 +++++++++++++++++-----
.../apache/ambari/server/view/ViewRegistry.java | 21 ++++++++++--
.../ambari/server/view/ViewExtractorTest.java | 33 ++++++++++++++++--
.../ambari/server/view/ViewRegistryTest.java | 3 +-
6 files changed, 105 insertions(+), 15 deletions(-)
diff --git a/ambari-server/conf/unix/ambari.properties
b/ambari-server/conf/unix/ambari.properties
index 873138d..80b2c74 100644
--- a/ambari-server/conf/unix/ambari.properties
+++ b/ambari-server/conf/unix/ambari.properties
@@ -130,6 +130,7 @@ views.http.x-xss-protection=1; mode=block
views.http.x-frame-options=SAMEORIGIN
views.http.x-content-type-options=nosniff
views.http.cache-control=no-store
+#views.additional.classpath=<comma separated list of dir and jars>
views.http.pragma=no-cache
mpacks.staging.path=$ROOT/var/lib/ambari-server/resources/mpacks
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index a7961bc..352b9ae 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -2449,6 +2449,15 @@ public class Configuration {
"views.http.cache-control", "no-store");
/**
+ * The value that is additional classpath for the views. It will take comma
separated paths. If the individual path is jar
+ * it will be included otherwise if it is a directory then all the files
inside it will be included in the classpath. Directories
+ * WILL NOT BE traversed recursively
+ */
+ @Markdown(description = "Additional class path added to each Ambari View.
Comma separated jars or directories")
+ public static final ConfigurationProperty<String>
VIEWS_ADDITIONAL_CLASSPATH_VALUE = new ConfigurationProperty<>(
+ "views.additional.classpath", "");
+
+ /**
* The value that will be used to set the {@code PRAGMA} HTTP response
header.
* HTTP response header for Ambari View requests.
*/
@@ -3815,6 +3824,19 @@ public class Configuration {
}
/**
+ * Get the comma separated additional classpath, that should be added to
view's classloader.
+ * <p/>
+ * By default it will be empty. i.e. no additional classpath.
+ * If present it will be comma separated path entries. Each entry can be a
file or a directory.
+ * If entry is a file it will be added as it is.
+ * If entry is a directory, all the files inside this directory will be
added to the classpath.
+ * @return the view's additional classpath value - null or "" indicates that
the value is not set
+ */
+ public String getViewsAdditionalClasspath() {
+ return getProperty(VIEWS_ADDITIONAL_CLASSPATH_VALUE);
+ }
+
+ /**
* Get the value that should be set for the <code>Pragma</code> HTTP
response header for Ambari Views.
* <p/>
* By default this will be <code>no-cache</code>. For example:
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewExtractor.java
b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewExtractor.java
index 0dabf90..07a42d5 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewExtractor.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewExtractor.java
@@ -27,6 +27,7 @@ import javax.inject.Inject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
@@ -63,11 +64,12 @@ public class ViewExtractor {
* @param viewArchive the view archive file
* @param archiveDir the view archive directory
*
+ * @param viewsAdditionalClasspath: list of additional paths to be added to
every view's classpath
* @return the class loader for the archive classes
*
* @throws ExtractionException if the archive can not be extracted
*/
- public ClassLoader extractViewArchive(ViewEntity view, File viewArchive,
File archiveDir)
+ public ClassLoader extractViewArchive(ViewEntity view, File viewArchive,
File archiveDir, List<File> viewsAdditionalClasspath)
throws ExtractionException {
String archivePath = archiveDir.getAbsolutePath();
@@ -158,7 +160,7 @@ public class ViewExtractor {
ViewConfig viewConfig =
archiveUtility.getViewConfigFromExtractedArchive(archivePath, false);
- return getArchiveClassLoader(viewConfig, archiveDir);
+ return getArchiveClassLoader(viewConfig, archiveDir,
viewsAdditionalClasspath);
} catch (Exception e) {
String msg = "Caught exception trying to extract the view archive " +
archivePath + ".";
@@ -187,7 +189,7 @@ public class ViewExtractor {
// ----- archiveUtility methods
----------------------------------------------------
// get a class loader for the given archive directory
- private ClassLoader getArchiveClassLoader(ViewConfig viewConfig, File
archiveDir)
+ private ClassLoader getArchiveClassLoader(ViewConfig viewConfig, File
archiveDir, List<File> viewsAdditionalClasspath)
throws IOException {
String archivePath = archiveDir.getAbsolutePath();
@@ -200,9 +202,34 @@ public class ViewExtractor {
urlList.add(classesDir.toURI().toURL());
}
+ // include libs in additional classpath
+ for (File file : viewsAdditionalClasspath) {
+ if (file.isDirectory()) {
+ // add all files inside this dir.
+ addDirToClasspath(urlList, file);
+ } else if (file.isFile()) {
+ urlList.add(file.toURI().toURL());
+ }
+ }
+
// include any libraries in the lib directory
String libPath = archivePath + File.separator + ARCHIVE_LIB_DIR;
- File libDir = archiveUtility.getFile(libPath);
+ File libDir = archiveUtility.getFile(libPath);
+ addDirToClasspath(urlList, libDir);
+
+ // include the archive directory
+ urlList.add(archiveDir.toURI().toURL());
+
+ LOG.trace("classpath for view {} is : {}", viewConfig.getName(), urlList);
+ return new ViewClassLoader(viewConfig, urlList.toArray(new
URL[urlList.size()]));
+ }
+
+ /**
+ * Add all the files in libDir to urlList ignoring directories.
+ * @param urlList: the list to which all paths needs to be appended
+ * @param libDir: the path of which all the files needs to be appended to
urlList
+ */
+ private void addDirToClasspath(List<URL> urlList, File libDir) throws
MalformedURLException {
if (libDir.exists()) {
File[] files = libDir.listFiles();
if (files != null) {
@@ -213,11 +240,6 @@ public class ViewExtractor {
}
}
}
-
- // include the archive directory
- urlList.add(archiveDir.toURI().toURL());
-
- return new ViewClassLoader(viewConfig, urlList.toArray(new
URL[urlList.size()]));
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
index 9d461e5..7d23451 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java
@@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -1803,7 +1804,9 @@ public class ViewRegistry {
try {
// extract the archive and get the class loader
- ClassLoader cl = extractor.extractViewArchive(viewDefinition,
archiveFile, extractedArchiveDirFile);
+ List<File> additionalPaths = getViewsAdditionalClasspath(configuration);
+
+ ClassLoader cl = extractor.extractViewArchive(viewDefinition,
archiveFile, extractedArchiveDirFile, additionalPaths);
configureViewLogging(viewDefinition, cl);
@@ -1843,6 +1846,19 @@ public class ViewRegistry {
}
}
+ private static List<File> getViewsAdditionalClasspath(Configuration
configuration) {
+ String viewsAdditionalClasspath =
configuration.getViewsAdditionalClasspath();
+ List<File> additionalPaths = new LinkedList<>();
+ if(null != viewsAdditionalClasspath &&
!viewsAdditionalClasspath.trim().isEmpty()) {
+ String[] paths = viewsAdditionalClasspath.trim().split(",");
+ for(String path : paths) {
+ if(null != path && !path.trim().isEmpty())
+ additionalPaths.add(new File(path));
+ }
+ }
+ return additionalPaths;
+ }
+
private void migrateDataFromPreviousVersion(ViewEntity viewDefinition,
String serverVersion) {
if (!viewDefinitions.containsKey(viewDefinition.getName())) { // migrate
only registered views to avoid recursive calls
LOG.debug("Cancel auto migration of not loaded view: " +
viewDefinition.getName() + ".");
@@ -2111,7 +2127,8 @@ public class ViewRegistry {
if (!systemOnly || viewDefinition.isSystem()) {
ClassLoader classLoader = null;
try {
- classLoader = extractor.extractViewArchive(viewDefinition,
archiveFile, extractedArchiveDirFile);
+ List<File> additionalPaths =
getViewsAdditionalClasspath(configuration);
+ classLoader = extractor.extractViewArchive(viewDefinition,
archiveFile, extractedArchiveDirFile, additionalPaths);
return true;
} finally {
if (classLoader != null && classLoader instanceof ViewClassLoader)
{
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/view/ViewExtractorTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/view/ViewExtractorTest.java
index 02ec916..12964cf 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/view/ViewExtractorTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/view/ViewExtractorTest.java
@@ -35,8 +35,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
@@ -79,6 +81,13 @@ public class ViewExtractorTest {
@Test
public void testExtractViewArchive() throws Exception {
+ File addDirPath = createNiceMock(File.class);
+ File addDirPathFile1 = createNiceMock(File.class);
+ File addDirPathFile2 = createNiceMock(File.class);
+ File addDirPath2 = createNiceMock(File.class);
+ File addFilePath = createNiceMock(File.class);
+ List<File> viewsAdditionalClasspath = Arrays.asList(addDirPath,
addDirPath2, addFilePath);
+
ResourceTypeEntity resourceTypeEntity = new ResourceTypeEntity();
resourceTypeEntity.setId(10);
resourceTypeEntity.setName("MY_VIEW{1.0.0}");
@@ -131,14 +140,32 @@ public class ViewExtractorTest {
expect(libDir.listFiles()).andReturn(new File[]{fileEntry});
expect(fileEntry.toURI()).andReturn(new URI("file:./"));
+ expect(addDirPath.isDirectory()).andReturn(true);
+ expect(addDirPath.exists()).andReturn(true);
+ expect(addDirPath.listFiles()).andReturn(new File[]{addDirPathFile1,
addDirPathFile2});
+ expect(addDirPathFile1.isDirectory()).andReturn(false);
+ expect(addDirPathFile1.toURI()).andReturn(new URI("file://file1"));
+ expect(addDirPathFile2.isDirectory()).andReturn(false);
+ expect(addDirPathFile2.toURI()).andReturn(new URI("file://file2"));
+
+ expect(addDirPath2.isDirectory()).andReturn(true);
+ expect(addDirPath2.exists()).andReturn(true);
+ expect(addDirPath2.listFiles()).andReturn(new File[]{});
+
+ expect(addFilePath.isDirectory()).andReturn(false);
+ expect(addFilePath.isFile()).andReturn(true);
+ expect(addFilePath.toURI()).andReturn(new URI("file://file3"));
+
replay(extractedArchiveDir, viewArchive, archiveDir, entryFile,
classesDir, libDir, metaInfDir, viewJarFile,
- jarEntry, fos, configuration, viewDir, fileEntry, viewDAO);
+ jarEntry, fos, configuration, viewDir, fileEntry, viewDAO,
+ addDirPath, addDirPathFile1, addDirPathFile2, addDirPath2,
addFilePath);
ViewExtractor viewExtractor = getViewExtractor(viewDefinition);
- viewExtractor.extractViewArchive(viewDefinition, viewArchive, archiveDir);
+ viewExtractor.extractViewArchive(viewDefinition, viewArchive, archiveDir,
viewsAdditionalClasspath);
verify(extractedArchiveDir, viewArchive, archiveDir, entryFile,
classesDir, libDir, metaInfDir, viewJarFile,
- jarEntry, fos, configuration, viewDir, fileEntry, viewDAO);
+ jarEntry, fos, configuration, viewDir, fileEntry, viewDAO,
+ addDirPath, addDirPathFile1, addDirPathFile2, addDirPath2,
addFilePath);
}
@Test
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
index 6a17d26..dc65d06 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/view/ViewRegistryTest.java
@@ -1493,6 +1493,7 @@ public class ViewRegistryTest {
File viewDir = createNiceMock(File.class);
File extractedArchiveDir = createNiceMock(File.class);
File viewArchive = createNiceMock(File.class);
+
File archiveDir = createNiceMock(File.class);
File entryFile = createNiceMock(File.class);
File classesDir = createNiceMock(File.class);
@@ -1589,7 +1590,7 @@ public class ViewRegistryTest {
else {
expect(viewExtractor.ensureExtractedArchiveDirectory("/var/lib/ambari-server/resources/views/work")).andReturn(true);
}
- expect(viewExtractor.extractViewArchive(capture(viewEntityCapture),
eq(viewArchive), eq(archiveDir))).andReturn(null);
+ expect(viewExtractor.extractViewArchive(capture(viewEntityCapture),
eq(viewArchive), eq(archiveDir), anyObject(List.class))).andReturn(null);
// replay mocks
replay(configuration, viewDir, extractedArchiveDir, viewArchive,
archiveDir, entryFile, classesDir,