You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ni...@apache.org on 2018/08/31 03:52:55 UTC

[ambari] branch trunk updated: AMBARI-24231 : Adding additional jars in classpath of ambari views (nitirajrathore) (#1657)

This is an automated email from the ASF dual-hosted git repository.

nitiraj pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 47a9fe4  AMBARI-24231 : Adding additional jars in classpath of ambari views (nitirajrathore) (#1657)
47a9fe4 is described below

commit 47a9fe42d5d47bf86673284b976b4f2fd11d03f7
Author: nitirajrathore <ni...@gmail.com>
AuthorDate: Fri Aug 31 09:22:53 2018 +0530

    AMBARI-24231 : Adding additional jars in classpath of ambari views (nitirajrathore) (#1657)
    
    * 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,