This is an automated email from the ASF dual-hosted git repository.
nitiraj pushed a commit to branch branch-2.7
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/branch-2.7 by this push:
new 77d348c AMBARI-24231 : Adding additional jars in classpath of ambari
views (nitirajrathore) (#2161)
77d348c is described below
commit 77d348c6dd8173b01f3319a61ca757e4fd9d0182
Author: nitirajrathore <[email protected]>
AuthorDate: Fri Aug 31 09:21:45 2018 +0530
AMBARI-24231 : Adding additional jars in classpath of ambari views
(nitirajrathore) (#2161)
* 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 ea137fc..4e189a4 100644
--- a/ambari-server/conf/unix/ambari.properties
+++ b/ambari-server/conf/unix/ambari.properties
@@ -124,6 +124,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
views.http.charset=utf-8
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 e2c3953..09d90e8 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
@@ -2269,6 +2269,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.
*/
@@ -3747,6 +3756,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 d729942..b9e95eb 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
@@ -20,6 +20,7 @@ package org.apache.ambari.server.view;
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;
@@ -64,11 +65,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();
@@ -159,7 +161,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 + ".";
@@ -188,7 +190,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();
@@ -201,9 +203,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) {
@@ -214,11 +241,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 cd871b3..8e573e4 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
@@ -29,6 +29,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;
@@ -1814,7 +1815,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);
@@ -1854,6 +1857,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());
@@ -2110,7 +2126,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 instanceof Closeable) {
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 3162b50..d06eca0 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
@@ -33,8 +33,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;
@@ -80,6 +82,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}");
@@ -132,14 +141,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 7703aaf..8e7f8b0 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
@@ -1517,6 +1517,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);
@@ -1613,7 +1614,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,