This is an automated email from the ASF dual-hosted git repository. vihangk1 pushed a commit to branch branch-3 in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/branch-3 by this push: new e648a5518a1 HIVE-27508 : Backport of HIVE-21584 to branch-3 (Aman Raj, reviewed by Vihang Karajgaonkar) e648a5518a1 is described below commit e648a5518a1106a87abd2a21da92f63a5e6884fc Author: Aman Raj <104416558+amanraj2...@users.noreply.github.com> AuthorDate: Sat Aug 5 10:42:33 2023 +0530 HIVE-27508 : Backport of HIVE-21584 to branch-3 (Aman Raj, reviewed by Vihang Karajgaonkar) --- .../src/java/org/apache/hive/beeline/Commands.java | 2 +- .../org/apache/hadoop/hive/common/JavaUtils.java | 57 ++------- .../hive/llap/daemon/impl/FunctionLocalizer.java | 18 ++- .../hadoop/hive/ql/exec/AddToClassPathAction.java | 87 +++++++++++++ .../hive/ql/exec/SerializationUtilities.java | 120 ++++++++++------- .../org/apache/hadoop/hive/ql/exec/Utilities.java | 97 +++++++------- .../apache/hadoop/hive/ql/exec/mr/ExecDriver.java | 7 +- .../apache/hadoop/hive/ql/exec/mr/ExecMapper.java | 22 +--- .../apache/hadoop/hive/ql/exec/mr/ExecReducer.java | 20 +-- .../hive/ql/exec/spark/SparkRecordHandler.java | 29 ++--- .../hadoop/hive/ql/exec/tez/RecordProcessor.java | 16 +-- .../hadoop/hive/ql/session/SessionState.java | 57 +++++---- .../hive/ql/exec/TestAddToClassPathAction.java | 142 +++++++++++++++++++++ .../hive/spark/client/SparkClientUtilities.java | 23 +++- .../hive/metastore/utils/MetaStoreUtils.java | 20 ++- 15 files changed, 456 insertions(+), 261 deletions(-) diff --git a/beeline/src/java/org/apache/hive/beeline/Commands.java b/beeline/src/java/org/apache/hive/beeline/Commands.java index 851042f3f9d..f14564a81ac 100644 --- a/beeline/src/java/org/apache/hive/beeline/Commands.java +++ b/beeline/src/java/org/apache/hive/beeline/Commands.java @@ -169,7 +169,7 @@ public class Commands { return false; } - URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { beeLine.debug(jarPath + " is added to the local beeline."); URLClassLoader newClassLoader = new URLClassLoader(new URL[]{p.toURL()}, classLoader); diff --git a/common/src/java/org/apache/hadoop/hive/common/JavaUtils.java b/common/src/java/org/apache/hadoop/hive/common/JavaUtils.java index 54d6b65fa17..93f591fe2a2 100644 --- a/common/src/java/org/apache/hadoop/hive/common/JavaUtils.java +++ b/common/src/java/org/apache/hadoop/hive/common/JavaUtils.java @@ -18,12 +18,8 @@ package org.apache.hadoop.hive.common; -import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; -import java.io.PrintStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.URLClassLoader; import java.util.Arrays; import java.util.List; @@ -38,22 +34,6 @@ import org.slf4j.LoggerFactory; */ public final class JavaUtils { private static final Logger LOG = LoggerFactory.getLogger(JavaUtils.class); - private static final Method SUN_MISC_UTIL_RELEASE; - - static { - if (Closeable.class.isAssignableFrom(URLClassLoader.class)) { - SUN_MISC_UTIL_RELEASE = null; - } else { - Method release = null; - try { - Class<?> clazz = Class.forName("sun.misc.ClassLoaderUtil"); - release = clazz.getMethod("releaseLoader", URLClassLoader.class); - } catch (Exception e) { - // ignore - } - SUN_MISC_UTIL_RELEASE = release; - } - } /** * Standard way of getting classloader in Hive code (outside of Hadoop). @@ -91,8 +71,10 @@ public final class JavaUtils { try { closeClassLoader(current); } catch (IOException e) { - LOG.info("Failed to close class loader " + current + - Arrays.toString(((URLClassLoader) current).getURLs()), e); + String detailedMessage = current instanceof URLClassLoader ? + Arrays.toString(((URLClassLoader) current).getURLs()) : + ""; + LOG.info("Failed to close class loader " + current + " " + detailedMessage, e); } } return true; @@ -108,35 +90,12 @@ public final class JavaUtils { return current == stop; } - // best effort to close - // see https://issues.apache.org/jira/browse/HIVE-3969 for detail public static void closeClassLoader(ClassLoader loader) throws IOException { if (loader instanceof Closeable) { - ((Closeable)loader).close(); - } else if (SUN_MISC_UTIL_RELEASE != null && loader instanceof URLClassLoader) { - PrintStream outputStream = System.out; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - PrintStream newOutputStream = new PrintStream(byteArrayOutputStream); - try { - // SUN_MISC_UTIL_RELEASE.invoke prints to System.out - // So we're changing the outputstream for that call, - // and setting it back to original System.out when we're done - System.setOut(newOutputStream); - SUN_MISC_UTIL_RELEASE.invoke(null, loader); - String output = byteArrayOutputStream.toString("UTF8"); - LOG.debug(output); - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof IOException) { - throw (IOException)e.getTargetException(); - } - throw new IOException(e.getTargetException()); - } catch (Exception e) { - throw new IOException(e); - } - finally { - System.setOut(outputStream); - newOutputStream.close(); - } + ((Closeable) loader).close(); + } else { + LOG.warn("Ignoring attempt to close class loader ({}) -- not instance of UDFClassLoader.", + loader == null ? "mull" : loader.getClass().getSimpleName()); } LogFactory.release(loader); } diff --git a/llap-server/src/java/org/apache/hadoop/hive/llap/daemon/impl/FunctionLocalizer.java b/llap-server/src/java/org/apache/hadoop/hive/llap/daemon/impl/FunctionLocalizer.java index 2a6ef3a2461..136fe2a3b38 100644 --- a/llap-server/src/java/org/apache/hadoop/hive/llap/daemon/impl/FunctionLocalizer.java +++ b/llap-server/src/java/org/apache/hadoop/hive/llap/daemon/impl/FunctionLocalizer.java @@ -18,8 +18,10 @@ import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLClassLoader; +import java.security.AccessController; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; @@ -33,10 +35,10 @@ import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.hadoop.hive.metastore.api.Function; import org.apache.hadoop.hive.metastore.api.MetaException; import org.apache.hadoop.hive.metastore.api.ResourceUri; -import org.apache.hadoop.hive.metastore.HiveMetaStoreClient; +import org.apache.hadoop.hive.ql.exec.AddToClassPathAction; import org.apache.hadoop.hive.ql.exec.FunctionRegistry; import org.apache.hadoop.hive.ql.exec.FunctionTask; -import org.apache.hadoop.hive.ql.exec.Utilities; +import org.apache.hadoop.hive.ql.exec.UDFClassLoader; import org.apache.hadoop.hive.ql.exec.FunctionInfo.FunctionResource; import org.apache.hadoop.hive.ql.metadata.Hive; import org.apache.hadoop.hive.ql.metadata.HiveException; @@ -60,7 +62,7 @@ public class FunctionLocalizer implements GenericUDFBridge.UdfWhitelistChecker { private final Thread workThread; private final File localDir; private final Configuration conf; - private final URLClassLoader executorClassloader; + private final UDFClassLoader executorClassloader; private final IdentityHashMap<Class<?>, Boolean> allowedUdfClasses = new IdentityHashMap<>(); @@ -70,8 +72,9 @@ public class FunctionLocalizer implements GenericUDFBridge.UdfWhitelistChecker { public FunctionLocalizer(Configuration conf, String localDir) { this.conf = conf; this.localDir = new File(localDir, DIR_NAME); - this.executorClassloader = (URLClassLoader)Utilities.createUDFClassLoader( - (URLClassLoader)Thread.currentThread().getContextClassLoader(), new String[]{}); + AddToClassPathAction addAction = new AddToClassPathAction( + Thread.currentThread().getContextClassLoader(), Collections.emptyList(), true); + this.executorClassloader = AccessController.doPrivileged(addAction); this.workThread = new Thread(new Runnable() { @Override public void run() { @@ -223,7 +226,8 @@ public class FunctionLocalizer implements GenericUDFBridge.UdfWhitelistChecker { recentlyLocalizedJars.clear(); ClassLoader updatedCl = null; try { - updatedCl = Utilities.addToClassPath(executorClassloader, jars); + AddToClassPathAction addAction = new AddToClassPathAction(executorClassloader, Arrays.asList(jars)); + updatedCl = AccessController.doPrivileged(addAction); if (LOG.isInfoEnabled()) { LOG.info("Added " + jars.length + " jars to classpath"); } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/AddToClassPathAction.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/AddToClassPathAction.java new file mode 100644 index 00000000000..a2ffbb1f240 --- /dev/null +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/AddToClassPathAction.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hive.ql.exec; + +import java.net.URL; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +/** + * Helper class to create UDFClassLoader when running under a security manager. To create a class loader: + * > AddToClassPathAction addAction = new AddToClassPathAction(parentLoader, newPaths, true); + * > UDFClassLoader childClassLoader = AccessController.doPrivileged(addAction); + * To try to add to the class path of the existing class loader; call the above without forceNewClassLoader=true. + * Note that a class loader might be still created as fallback method. + * <p> + * This is slightly inconvenient, but forces the caller code to make the doPriviliged call, rather than us making the + * call on the caller's behalf, in accordance with the security guidelines at: + * https://docs.oracle.com/javase/8/docs/technotes/guides/security/doprivileged.html + */ +public class AddToClassPathAction implements PrivilegedAction<UDFClassLoader> { + + private final ClassLoader parentLoader; + private final Collection<String> newPaths; + private final boolean forceNewClassLoader; + + public AddToClassPathAction(ClassLoader parentLoader, Collection<String> newPaths, boolean forceNewClassLoader) { + this.parentLoader = parentLoader; + this.newPaths = newPaths != null ? newPaths : Collections.emptyList(); + this.forceNewClassLoader = forceNewClassLoader; + if (parentLoader == null) { + throw new IllegalArgumentException("UDFClassLoader is not designed to be a bootstrap class loader!"); + } + } + + public AddToClassPathAction(ClassLoader parentLoader, Collection<String> newPaths) { + this(parentLoader, newPaths, false); + } + + @Override + public UDFClassLoader run() { + if (useExistingClassLoader()) { + final UDFClassLoader udfClassLoader = (UDFClassLoader) parentLoader; + for (String path : newPaths) { + udfClassLoader.addURL(Utilities.urlFromPathString(path)); + } + return udfClassLoader; + } else { + return createUDFClassLoader(); + } + } + + private boolean useExistingClassLoader() { + if (!forceNewClassLoader && parentLoader instanceof UDFClassLoader) { + final UDFClassLoader udfClassLoader = (UDFClassLoader) parentLoader; + // The classloader may have been closed, Cannot add to the same instance + return !udfClassLoader.isClosed(); + } + // Cannot use the same classloader if it is not an instance of {@code UDFClassLoader}, or new loader was explicily + // requested + return false; + } + + private UDFClassLoader createUDFClassLoader() { + return new UDFClassLoader(newPaths.stream() + .map(Utilities::urlFromPathString) + .filter(Objects::nonNull) + .toArray(URL[]::new), parentLoader); + } +} diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/SerializationUtilities.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/SerializationUtilities.java index ed1566ff19e..5101a8a9b34 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/SerializationUtilities.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/SerializationUtilities.java @@ -365,67 +365,89 @@ public class SerializationUtilities { } /** - * Supports sublists created via {@link ArrayList#subList(int, int)} since java7 (oracle jdk, - * represented by <code>java.util.ArrayList$SubList</code>). + * Supports sublists created via {@link ArrayList#subList(int, int)} since java7 and {@link LinkedList#subList(int, int)} since java9 (openjdk). * This is from kryo-serializers package. */ private static class ArrayListSubListSerializer extends com.esotericsoftware.kryo.Serializer<List<?>> { - private Field _parentField; - private Field _parentOffsetField; - private Field _sizeField; - - public ArrayListSubListSerializer() { - try { - final Class<?> clazz = Class.forName("java.util.ArrayList$SubList"); - _parentField = clazz.getDeclaredField("parent"); - _parentOffsetField = clazz.getDeclaredField( "parentOffset" ); - _sizeField = clazz.getDeclaredField( "size" ); - _parentField.setAccessible( true ); - _parentOffsetField.setAccessible( true ); - _sizeField.setAccessible( true ); - } catch (final Exception e) { - throw new RuntimeException(e); - } + private Field _parentField; + private Field _parentOffsetField; + private Field _sizeField; + + public ArrayListSubListSerializer() { + try { + final Class<?> clazz = Class.forName("java.util.ArrayList$SubList"); + _parentField = getParentField(clazz); + _parentOffsetField = getOffsetField(clazz); + _sizeField = clazz.getDeclaredField( "size" ); + _parentField.setAccessible( true ); + _parentOffsetField.setAccessible( true ); + _sizeField.setAccessible( true ); + } catch (final Exception e) { + throw new RuntimeException(e); } + } - @Override - public List<?> read(final Kryo kryo, final Input input, final Class<List<?>> clazz) { - kryo.reference(FAKE_REFERENCE); - final List<?> list = (List<?>) kryo.readClassAndObject(input); - final int fromIndex = input.readInt(true); - final int toIndex = input.readInt(true); - return list.subList(fromIndex, toIndex); + private static Field getParentField(Class clazz) throws NoSuchFieldException { + try { + // java 9+ + return clazz.getDeclaredField("root"); + } catch(NoSuchFieldException e) { + return clazz.getDeclaredField("parent"); } + } - @Override - public void write(final Kryo kryo, final Output output, final List<?> obj) { - try { - kryo.writeClassAndObject(output, _parentField.get(obj)); - final int parentOffset = _parentOffsetField.getInt( obj ); - final int fromIndex = parentOffset; - output.writeInt(fromIndex, true); - final int toIndex = fromIndex + _sizeField.getInt( obj ); - output.writeInt(toIndex, true); - } catch (final Exception e) { - throw new RuntimeException(e); - } + private static Field getOffsetField(Class<?> clazz) throws NoSuchFieldException { + try { + // up to jdk8 (which also has an "offset" field (we don't need) - therefore we check "parentOffset" first + return clazz.getDeclaredField( "parentOffset" ); + } catch (NoSuchFieldException e) { + // jdk9+ only has "offset" which is the parent offset + return clazz.getDeclaredField( "offset" ); } + } - @Override - public List<?> copy(final Kryo kryo, final List<?> original) { - try { - kryo.reference(FAKE_REFERENCE); - final List<?> list = (List<?>) _parentField.get(original); - final int parentOffset = _parentOffsetField.getInt( original ); - final int fromIndex = parentOffset; - final int toIndex = fromIndex + _sizeField.getInt( original ); - return kryo.copy(list).subList(fromIndex, toIndex); - } catch (final Exception e) { - throw new RuntimeException(e); - } + @Override + public List<?> read(final Kryo kryo, final Input input, final Class<List<?>> clazz) { + kryo.reference(FAKE_REFERENCE); + final List<?> list = (List<?>) kryo.readClassAndObject(input); + final int fromIndex = input.readInt(true); + final int toIndex = input.readInt(true); + return list.subList(fromIndex, toIndex); + } + + @Override + public void write(final Kryo kryo, final Output output, final List<?> obj) { + try { + kryo.writeClassAndObject(output, _parentField.get(obj)); + final int parentOffset = _parentOffsetField.getInt( obj ); + final int fromIndex = parentOffset; + output.writeInt(fromIndex, true); + final int toIndex = fromIndex + _sizeField.getInt( obj ); + output.writeInt(toIndex, true); + } catch (final RuntimeException e) { + // Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write... + // handles SerializationException specifically (resizing the buffer)... + throw e; + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public List<?> copy(final Kryo kryo, final List<?> original) { + kryo.reference(FAKE_REFERENCE); + try { + final List<?> list = (List<?>) _parentField.get(original); + final int parentOffset = _parentOffsetField.getInt( original ); + final int fromIndex = parentOffset; + final int toIndex = fromIndex + _sizeField.getInt( original ); + return kryo.copy(list).subList(fromIndex, toIndex); + } catch(final Exception e) { + throw new RuntimeException(e); } } + } /** * A kryo {@link Serializer} for lists created via {@link Arrays#asList(Object...)}. diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java index bfa3bfde452..26faf55fb8d 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java @@ -42,6 +42,7 @@ import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.net.URLDecoder; +import java.security.AccessController; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; @@ -420,8 +421,10 @@ public final class Utilities { // threads, should be unnecessary while SPARK-5377 is resolved. String addedJars = conf.get(HIVE_ADDED_JARS); if (StringUtils.isNotEmpty(addedJars)) { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - ClassLoader newLoader = addToClassPath(loader, addedJars.split(";")); + AddToClassPathAction addAction = new AddToClassPathAction( + Thread.currentThread().getContextClassLoader(), Arrays.asList(addedJars.split(";")) + ); + ClassLoader newLoader = AccessController.doPrivileged(addAction); Thread.currentThread().setContextClassLoader(newLoader); kryo.setClassLoader(newLoader); } @@ -1546,9 +1549,8 @@ public final class Utilities { * Check the existence of buckets according to bucket specification. Create empty buckets if * needed. * - * @param hconf + * @param hconf The definition of the FileSink. * @param paths A list of empty buckets to create - * @param conf The definition of the FileSink. * @param reporter The mapreduce reporter object * @throws HiveException * @throws IOException @@ -2002,7 +2004,7 @@ public final class Utilities { * @param onestr path string * @return */ - private static URL urlFromPathString(String onestr) { + static URL urlFromPathString(String onestr) { URL oneurl = null; try { if (StringUtils.indexOf(onestr, "file:/") == 0) { @@ -2016,59 +2018,26 @@ public final class Utilities { return oneurl; } - private static boolean useExistingClassLoader(ClassLoader cl) { - if (!(cl instanceof UDFClassLoader)) { - // Cannot use the same classloader if it is not an instance of {@code UDFClassLoader} - return false; - } - final UDFClassLoader udfClassLoader = (UDFClassLoader) cl; - if (udfClassLoader.isClosed()) { - // The classloader may have been closed, Cannot add to the same instance - return false; - } - return true; - } - /** - * Add new elements to the classpath. - * - * @param newPaths - * Array of classpath elements - */ - public static ClassLoader addToClassPath(ClassLoader cloader, String[] newPaths) { - final URLClassLoader loader = (URLClassLoader) cloader; - if (useExistingClassLoader(cloader)) { - final UDFClassLoader udfClassLoader = (UDFClassLoader) loader; - for (String path : newPaths) { - udfClassLoader.addURL(urlFromPathString(path)); - } - return udfClassLoader; - } else { - return createUDFClassLoader(loader, newPaths); - } - } - - public static ClassLoader createUDFClassLoader(URLClassLoader loader, String[] newPaths) { - final Set<URL> curPathsSet = Sets.newHashSet(loader.getURLs()); - final List<URL> curPaths = Lists.newArrayList(curPathsSet); - for (String onestr : newPaths) { - final URL oneurl = urlFromPathString(onestr); - if (oneurl != null && !curPathsSet.contains(oneurl)) { - curPaths.add(oneurl); - } - } - return new UDFClassLoader(curPaths.toArray(new URL[0]), loader); - } - - /** - * remove elements from the classpath. + * Remove elements from the classpath, if possible. This will only work if the current thread context class loader is + * an UDFClassLoader (i.e. if we have created it). * * @param pathsToRemove * Array of classpath elements */ public static void removeFromClassPath(String[] pathsToRemove) throws IOException { Thread curThread = Thread.currentThread(); - URLClassLoader loader = (URLClassLoader) curThread.getContextClassLoader(); + ClassLoader currentLoader = curThread.getContextClassLoader(); + // If current class loader is NOT UDFClassLoader, then it is a system class loader, we should not mess with it. + if (!(currentLoader instanceof UDFClassLoader)) { + LOG.warn("Ignoring attempt to manipulate {}; probably means we have closed more UDF loaders than opened.", + currentLoader == null ? "null" : currentLoader.getClass().getSimpleName()); + return; + } + // Otherwise -- for UDFClassLoaders -- we close the current one and create a new one, with more limited class path. + + UDFClassLoader loader = (UDFClassLoader) currentLoader; + Set<URL> newPath = new HashSet<URL>(Arrays.asList(loader.getURLs())); for (String onestr : pathsToRemove) { @@ -2078,9 +2047,9 @@ public final class Utilities { } } JavaUtils.closeClassLoader(loader); - // This loader is closed, remove it from cached registry loaders to avoid removing it again. + // This loader is closed, remove it from cached registry loaders to avoid removing it again. Registry reg = SessionState.getRegistry(); - if(reg != null) { + if (reg != null) { reg.removeFromUDFLoaders(loader); } @@ -4523,4 +4492,26 @@ public final class Utilities { } return passwd; } + + /** + * Logs the class paths of the job class loader and the thread context class loader to the passed logger. + * Checks both loaders if getURLs method is available; if not, prints a message about this (instead of the class path) + * + * Note: all messages will always be logged with INFO log level. + */ + public static void tryLoggingClassPaths(JobConf job, Logger logger) { + if (logger != null && logger.isInfoEnabled()) { + tryToLogClassPath("conf", job.getClassLoader(), logger); + tryToLogClassPath("thread", Thread.currentThread().getContextClassLoader(), logger); + } + } + + private static void tryToLogClassPath(String prefix, ClassLoader loader, Logger logger) { + if(loader instanceof URLClassLoader) { + logger.info("{} class path = {}", prefix, Arrays.asList(((URLClassLoader) loader).getURLs()).toString()); + } else { + logger.info("{} class path = unavailable for {}", prefix, + loader == null ? "null" : loader.getClass().getSimpleName()); + } + } } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecDriver.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecDriver.java index 7ff8ddc6a0a..65c830cf1b6 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecDriver.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecDriver.java @@ -24,7 +24,9 @@ import java.io.OutputStream; import java.io.Serializable; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; +import java.security.AccessController; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -33,6 +35,7 @@ import java.util.Map; import java.util.Properties; import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.hive.ql.exec.AddToClassPathAction; import org.apache.hadoop.hive.ql.exec.SerializationUtilities; import org.apache.hadoop.hive.ql.log.LogDivertAppenderForTest; import org.apache.hadoop.mapreduce.MRJobConfig; @@ -752,7 +755,9 @@ public class ExecDriver extends Task<MapredWork> implements Serializable, Hadoop // see also - code in CliDriver.java ClassLoader loader = conf.getClassLoader(); if (StringUtils.isNotBlank(libjars)) { - loader = Utilities.addToClassPath(loader, StringUtils.split(libjars, ",")); + AddToClassPathAction addAction = new AddToClassPathAction( + loader, Arrays.asList(StringUtils.split(libjars, ","))); + loader = AccessController.doPrivileged(addAction); } conf.setClassLoader(loader); // Also set this to the Thread ContextClassLoader, so new threads will diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecMapper.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecMapper.java index 91868a46670..a5beb633bcb 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecMapper.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecMapper.java @@ -19,15 +19,9 @@ package org.apache.hadoop.hive.ql.exec.mr; import java.io.IOException; -import java.net.URLClassLoader; -import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.hadoop.hive.ql.plan.PartitionDesc; -import org.apache.hadoop.hive.ql.plan.TableDesc; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.ql.CompilationOpContext; @@ -41,6 +35,8 @@ import org.apache.hadoop.hive.ql.exec.vector.VectorMapOperator; import org.apache.hadoop.hive.ql.plan.MapWork; import org.apache.hadoop.hive.ql.plan.MapredLocalWork; import org.apache.hadoop.hive.ql.plan.OperatorDesc; +import org.apache.hadoop.hive.ql.plan.PartitionDesc; +import org.apache.hadoop.hive.ql.plan.TableDesc; import org.apache.hadoop.io.Writable; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.MapReduceBase; @@ -48,6 +44,8 @@ import org.apache.hadoop.mapred.Mapper; import org.apache.hadoop.mapred.OutputCollector; import org.apache.hadoop.mapred.Reporter; import org.apache.hadoop.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ExecMapper is the generic Map class for Hive. Together with ExecReducer it is @@ -76,17 +74,7 @@ public class ExecMapper extends MapReduceBase implements Mapper { @Override public void configure(JobConf job) { execContext = new ExecMapperContext(job); - // Allocate the bean at the beginning - - try { - l4j.info("conf classpath = " - + Arrays.asList(((URLClassLoader) job.getClassLoader()).getURLs())); - l4j.info("thread classpath = " - + Arrays.asList(((URLClassLoader) Thread.currentThread() - .getContextClassLoader()).getURLs())); - } catch (Exception e) { - l4j.info("cannot get classpath: " + e.getMessage()); - } - + Utilities.tryLoggingClassPaths(job, l4j); setDone(false); try { diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecReducer.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecReducer.java index 829006d3754..7ce1544839f 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecReducer.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/mr/ExecReducer.java @@ -19,16 +19,10 @@ package org.apache.hadoop.hive.ql.exec.mr; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.net.URLClassLoader; import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hive.ql.exec.MapredContext; import org.apache.hadoop.hive.ql.exec.Operator; import org.apache.hadoop.hive.ql.exec.Utilities; @@ -49,6 +43,8 @@ import org.apache.hadoop.mapred.Reducer; import org.apache.hadoop.mapred.Reporter; import org.apache.hadoop.util.ReflectionUtils; import org.apache.hadoop.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ExecReducer is the generic Reducer class for Hive. Together with ExecMapper it is @@ -94,17 +90,7 @@ public class ExecReducer extends MapReduceBase implements Reducer { ObjectInspector[] valueObjectInspector = new ObjectInspector[Byte.MAX_VALUE]; ObjectInspector keyObjectInspector; - if (LOG.isInfoEnabled()) { - try { - LOG.info("conf classpath = " - + Arrays.asList(((URLClassLoader) job.getClassLoader()).getURLs())); - LOG.info("thread classpath = " - + Arrays.asList(((URLClassLoader) Thread.currentThread() - .getContextClassLoader()).getURLs())); - } catch (Exception e) { - LOG.info("cannot get classpath: " + e.getMessage()); - } - } + Utilities.tryLoggingClassPaths(job, LOG); jc = job; ReduceWork gWork = Utilities.getReduceWork(job); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/spark/SparkRecordHandler.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/spark/SparkRecordHandler.java index cb5bd7ada2d..c5b33f89f13 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/spark/SparkRecordHandler.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/spark/SparkRecordHandler.java @@ -18,21 +18,23 @@ package org.apache.hadoop.hive.ql.exec.spark; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.util.Iterator; + import org.apache.hadoop.hive.ql.exec.MapredContext; +import org.apache.hadoop.hive.ql.exec.Utilities; import org.apache.hadoop.hive.ql.log.PerfLogger; import org.apache.hadoop.hive.ql.session.SessionState; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.OutputCollector; import org.apache.hadoop.mapred.Reporter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.Iterator; public abstract class SparkRecordHandler { protected static final String CLASS_NAME = SparkRecordHandler.class.getName(); @@ -60,16 +62,7 @@ public abstract class SparkRecordHandler { rp = reporter; LOG.info("maximum memory = " + memoryMXBean.getHeapMemoryUsage().getMax()); - - try { - LOG.info("conf classpath = " - + Arrays.asList(((URLClassLoader) job.getClassLoader()).getURLs())); - LOG.info("thread classpath = " - + Arrays.asList(((URLClassLoader) Thread.currentThread() - .getContextClassLoader()).getURLs())); - } catch (Exception e) { - LOG.info("cannot get classpath: " + e.getMessage()); - } + Utilities.tryLoggingClassPaths(job, LOG); } /** diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/tez/RecordProcessor.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/tez/RecordProcessor.java index 0ec7a04ce7a..86390963c85 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/tez/RecordProcessor.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/tez/RecordProcessor.java @@ -16,9 +16,7 @@ * limitations under the License. */ package org.apache.hadoop.hive.ql.exec.tez; -import java.net.URLClassLoader; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -80,19 +78,7 @@ public abstract class RecordProcessor extends InterruptibleProcessing { this.outputs = outputs; checkAbortCondition(); - - //log classpaths - try { - if (l4j.isDebugEnabled()) { - l4j.debug("conf classpath = " - + Arrays.asList(((URLClassLoader) jconf.getClassLoader()).getURLs())); - l4j.debug("thread classpath = " - + Arrays.asList(((URLClassLoader) Thread.currentThread() - .getContextClassLoader()).getURLs())); - } - } catch (Exception e) { - l4j.info("cannot get classpath: " + e.getMessage()); - } + Utilities.tryLoggingClassPaths(jconf, l4j); } /** diff --git a/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java b/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java index 56c32e8606e..0dc1dca6630 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/session/SessionState.java @@ -27,11 +27,12 @@ import java.io.PrintStream; import java.lang.management.ManagementFactory; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLClassLoader; +import java.security.AccessController; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -70,6 +71,7 @@ import org.apache.hadoop.hive.metastore.cache.CachedStore; import org.apache.hadoop.hive.metastore.conf.MetastoreConf; import org.apache.hadoop.hive.metastore.utils.MetaStoreUtils; import org.apache.hadoop.hive.ql.MapRedStats; +import org.apache.hadoop.hive.ql.exec.AddToClassPathAction; import org.apache.hadoop.hive.ql.exec.FunctionInfo; import org.apache.hadoop.hive.ql.exec.Registry; import org.apache.hadoop.hive.ql.exec.Utilities; @@ -414,7 +416,9 @@ public class SessionState { // classloader as parent can pollute the session. See HIVE-11878 parentLoader = SessionState.class.getClassLoader(); // Make sure that each session has its own UDFClassloader. For details see {@link UDFClassLoader} - final ClassLoader currentLoader = Utilities.createUDFClassLoader((URLClassLoader) parentLoader, new String[]{}); + AddToClassPathAction addAction = new AddToClassPathAction( + parentLoader, Collections.emptyList(), true); + final ClassLoader currentLoader = AccessController.doPrivileged(addAction); this.sessionConf.setClassLoader(currentLoader); resourceDownloader = new ResourceDownloader(conf, HiveConf.getVar(conf, ConfVars.DOWNLOADED_RESOURCES_DIR)); @@ -1305,6 +1309,21 @@ public class SessionState { } } + static void registerJars(List<String> newJars) throws IllegalArgumentException { + LogHelper console = getConsole(); + try { + AddToClassPathAction addAction = new AddToClassPathAction( + Thread.currentThread().getContextClassLoader(), newJars); + final ClassLoader newLoader = AccessController.doPrivileged(addAction); + Thread.currentThread().setContextClassLoader(newLoader); + SessionState.get().getConf().setClassLoader(newLoader); + console.printInfo("Added " + newJars + " to class path"); + } catch (Exception e) { + String message = "Unable to register " + newJars; + throw new IllegalArgumentException(message, e); + } + } + /** * Load the jars under the path specified in hive.aux.jars.path property. Add * the jars to the classpath so the local task can refer to them. @@ -1315,17 +1334,17 @@ public class SessionState { if (ArrayUtils.isEmpty(jarPaths)) { return; } - - URLClassLoader currentCLoader = - (URLClassLoader) SessionState.get().getConf().getClassLoader(); - currentCLoader = - (URLClassLoader) Utilities.addToClassPath(currentCLoader, jarPaths); + AddToClassPathAction addAction = new AddToClassPathAction( + SessionState.get().getConf().getClassLoader(), Arrays.asList(jarPaths) + ); + final ClassLoader currentCLoader = AccessController.doPrivileged(addAction); sessionConf.setClassLoader(currentCLoader); Thread.currentThread().setContextClassLoader(currentCLoader); } /** * Reload the jars under the path specified in hive.reloadable.aux.jars.path property. + * * @throws IOException */ public void loadReloadableAuxJars() throws IOException { @@ -1340,7 +1359,7 @@ public class SessionState { Set<String> jarPaths = FileUtils.getJarFilesByPath(renewableJarPath, sessionConf); // load jars under the hive.reloadable.aux.jars.path - if(!jarPaths.isEmpty()){ + if (!jarPaths.isEmpty()) { reloadedAuxJars.addAll(jarPaths); } @@ -1350,11 +1369,9 @@ public class SessionState { } if (reloadedAuxJars != null && !reloadedAuxJars.isEmpty()) { - URLClassLoader currentCLoader = - (URLClassLoader) SessionState.get().getConf().getClassLoader(); - currentCLoader = - (URLClassLoader) Utilities.addToClassPath(currentCLoader, - reloadedAuxJars.toArray(new String[0])); + AddToClassPathAction addAction = new AddToClassPathAction( + SessionState.get().getConf().getClassLoader(), reloadedAuxJars); + final ClassLoader currentCLoader = AccessController.doPrivileged(addAction); sessionConf.setClassLoader(currentCLoader); Thread.currentThread().setContextClassLoader(currentCLoader); } @@ -1362,20 +1379,6 @@ public class SessionState { preReloadableAuxJars.addAll(reloadedAuxJars); } - static void registerJars(List<String> newJars) throws IllegalArgumentException { - LogHelper console = getConsole(); - try { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - ClassLoader newLoader = Utilities.addToClassPath(loader, newJars.toArray(new String[0])); - Thread.currentThread().setContextClassLoader(newLoader); - SessionState.get().getConf().setClassLoader(newLoader); - console.printInfo("Added " + newJars + " to class path"); - } catch (Exception e) { - String message = "Unable to register " + newJars; - throw new IllegalArgumentException(message, e); - } - } - static boolean unregisterJar(List<String> jarsToUnregister) { LogHelper console = getConsole(); try { diff --git a/ql/src/test/org/apache/hadoop/hive/ql/exec/TestAddToClassPathAction.java b/ql/src/test/org/apache/hadoop/hive/ql/exec/TestAddToClassPathAction.java new file mode 100644 index 00000000000..e524bb5772a --- /dev/null +++ b/ql/src/test/org/apache/hadoop/hive/ql/exec/TestAddToClassPathAction.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hive.ql.exec; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.URL; +import java.security.AccessController; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +/** + * Minimal tests for AddToClassPathAction class. Most of the tests don't use + * {@link java.security.AccessController#doPrivileged(java.security.PrivilegedAction)}, + * presumably the tests will not be executed under security manager. + */ +public class TestAddToClassPathAction { + + private ClassLoader originalClassLoader; + + private static void assertURLsMatch(String message, List<String> expected, URL[] actual) { + List<String> actualStrings = Arrays.stream(actual).map(URL::toExternalForm).collect(Collectors.toList()); + assertEquals(message, expected, actualStrings); + } + + private static void assertURLsMatch(List<String> expected, URL[] actual) { + assertURLsMatch("", expected, actual); + } + + @Before + public void saveClassLoader() { + originalClassLoader = Thread.currentThread().getContextClassLoader(); + } + + @After + public void restoreClassLoader() { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + + @Test + public void testNullClassLoader() { + try { + new AddToClassPathAction(null, Collections.emptyList()); + fail("When pafrent class loader is null, IllegalArgumentException is expected!"); + } catch (IllegalArgumentException e) { + // pass + } + } + + @Test + public void testNullPaths() { + ClassLoader rootLoader = Thread.currentThread().getContextClassLoader(); + AddToClassPathAction action = new AddToClassPathAction(rootLoader, null); + UDFClassLoader childLoader = action.run(); + assertURLsMatch( + "When newPaths is null, loader shall be created normally with no extra paths.", + Collections.emptyList(), childLoader.getURLs()); + } + + @Test + public void testUseExisting() { + ClassLoader rootLoader = Thread.currentThread().getContextClassLoader(); + AddToClassPathAction action1 = new AddToClassPathAction(rootLoader, Arrays.asList("/a/1", "/c/3")); + UDFClassLoader parentLoader = action1.run(); + AddToClassPathAction action2 = new AddToClassPathAction(parentLoader, Arrays.asList("/b/2", "/d/4")); + UDFClassLoader childLoader = action2.run(); + assertSame( + "Normally, the existing class loader should be reused (not closed, no force new).", + parentLoader, childLoader); + assertURLsMatch( + "The class path of the class loader should be updated.", + Arrays.asList("file:/a/1", "file:/c/3", "file:/b/2", "file:/d/4"), childLoader.getURLs()); + } + + @Test + public void testClosed() throws IOException { + ClassLoader rootLoader = Thread.currentThread().getContextClassLoader(); + AddToClassPathAction action1 = new AddToClassPathAction(rootLoader, Arrays.asList("/a/1", "/c/3")); + UDFClassLoader parentLoader = action1.run(); + parentLoader.close(); + AddToClassPathAction action2 = new AddToClassPathAction(parentLoader, Arrays.asList("/b/2", "/d/4")); + UDFClassLoader childLoader = action2.run(); + assertNotSame( + "When the parent class loader is closed, a new instance must be created.", + parentLoader, childLoader); + assertURLsMatch(Arrays.asList("file:/b/2", "file:/d/4"), childLoader.getURLs()); + } + + @Test + public void testForceNew() { + ClassLoader rootLoader = Thread.currentThread().getContextClassLoader(); + AddToClassPathAction action1 = new AddToClassPathAction(rootLoader, Arrays.asList("/a/1", "/c/3")); + UDFClassLoader parentLoader = action1.run(); + AddToClassPathAction action2 = new AddToClassPathAction(parentLoader, Arrays.asList("/b/2", "/d/4"), true); + UDFClassLoader childLoader = action2.run(); + assertNotSame( + "When forceNewClassLoader is set, a new instance must be created.", + parentLoader, childLoader); + assertURLsMatch(Arrays.asList("file:/b/2", "file:/d/4"), childLoader.getURLs()); + } + + @Test + public void testLegalPaths() { + ClassLoader rootLoader = Thread.currentThread().getContextClassLoader(); + List<String> newPaths = Arrays.asList("file://a/aa", "c/cc", "/bb/b"); + String userDir = System.getProperty("user.dir"); + List<String> expectedURLs = Arrays.asList( + "file://a/aa", + "file:" + userDir + "/c/cc", + "file:/bb/b"); + AddToClassPathAction action = new AddToClassPathAction(rootLoader, newPaths); + UDFClassLoader loader = AccessController.doPrivileged(action); + assertURLsMatch(expectedURLs, loader.getURLs()); + } + +} diff --git a/spark-client/src/main/java/org/apache/hive/spark/client/SparkClientUtilities.java b/spark-client/src/main/java/org/apache/hive/spark/client/SparkClientUtilities.java index d3cb3dd7a1c..ca861400b49 100644 --- a/spark-client/src/main/java/org/apache/hive/spark/client/SparkClientUtilities.java +++ b/spark-client/src/main/java/org/apache/hive/spark/client/SparkClientUtilities.java @@ -27,6 +27,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -52,14 +53,30 @@ public class SparkClientUtilities { /** * Add new elements to the classpath. + * Returns currently known class paths as best effort. For system class loader, this may return empty. + * In such cases we will anyway create new child class loader in {@link #addToClassPath(Map, Configuration, File)}, + * so all new class paths will be added and next time we will have a URLClassLoader to work with. + */ + private static List<URL> getCurrentClassPaths(ClassLoader parentLoader) { + if(parentLoader instanceof URLClassLoader) { + return Lists.newArrayList(((URLClassLoader) parentLoader).getURLs()); + } else { + return Collections.emptyList(); + } + } + + /** + * Add new elements to the classpath by creating a child ClassLoader containing both old and new paths. + * This method supports downloading HDFS files to local FS if missing from cache or later timestamp. + * However, this method has no tricks working around HIVE-11878, like UDFClassLoader.... * * @param newPaths Map of classpath elements and corresponding timestamp * @return locally accessible files corresponding to the newPaths */ public static List<String> addToClassPath(Map<String, Long> newPaths, Configuration conf, File localTmpDir) throws Exception { - URLClassLoader loader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); - List<URL> curPath = Lists.newArrayList(loader.getURLs()); + ClassLoader parentLoader = Thread.currentThread().getContextClassLoader(); + List<URL> curPath = getCurrentClassPaths(parentLoader); List<String> localNewPaths = new ArrayList<>(); boolean newPathAdded = false; @@ -75,7 +92,7 @@ public class SparkClientUtilities { if (newPathAdded) { URLClassLoader newLoader = - new URLClassLoader(curPath.toArray(new URL[curPath.size()]), loader); + new URLClassLoader(curPath.toArray(new URL[curPath.size()]), parentLoader); Thread.currentThread().setContextClassLoader(newLoader); } return localNewPaths; diff --git a/standalone-metastore/src/main/java/org/apache/hadoop/hive/metastore/utils/MetaStoreUtils.java b/standalone-metastore/src/main/java/org/apache/hadoop/hive/metastore/utils/MetaStoreUtils.java index 8502e291a90..a729831c5e9 100644 --- a/standalone-metastore/src/main/java/org/apache/hadoop/hive/metastore/utils/MetaStoreUtils.java +++ b/standalone-metastore/src/main/java/org/apache/hadoop/hive/metastore/utils/MetaStoreUtils.java @@ -85,7 +85,7 @@ import java.security.MessageDigest; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -1123,6 +1123,19 @@ public class MetaStoreUtils { return HadoopThriftAuthBridge.getBridge().getHadoopSaslProperties(conf); } + /** + * Returns currently known class paths as best effort. For system class loader, this may return empty. + * In such cases we will anyway create new child class loader in {@link #addToClassPath(ClassLoader, String[])}, + * so all new class paths will be added and next time we will have a URLClassLoader to work with. + */ + private static List<URL> getCurrentClassPaths(ClassLoader parentLoader) { + if(parentLoader instanceof URLClassLoader) { + return Lists.newArrayList(((URLClassLoader) parentLoader).getURLs()); + } else { + return Collections.emptyList(); + } + } + /** * Add new elements to the classpath. * @@ -1130,8 +1143,7 @@ public class MetaStoreUtils { * Array of classpath elements */ public static ClassLoader addToClassPath(ClassLoader cloader, String[] newPaths) throws Exception { - URLClassLoader loader = (URLClassLoader) cloader; - List<URL> curPath = Arrays.asList(loader.getURLs()); + List<URL> curPath = getCurrentClassPaths(cloader); ArrayList<URL> newPath = new ArrayList<>(curPath.size()); // get a list with the current classpath components @@ -1147,7 +1159,7 @@ public class MetaStoreUtils { } } - return new URLClassLoader(curPath.toArray(new URL[0]), loader); + return new URLClassLoader(curPath.toArray(new URL[0]), cloader); } /**