/*
 * Licensed to Metamarkets Group Inc. (Metamarkets) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. Metamarkets 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.fs;

import io.druid.java.util.common.logger.Logger;

import java.io.IOException;

/**
 * This wrapper class is created to be able to access some of the the "protected" methods inside Hadoop's
 * FileSystem class. Those are supposed to become public eventually or more appropriate alternatives would be
 * provided.
 * This is a hack and should be removed when no longer necessary.
 */
public class HadoopFsWrapper
{
  private static final Logger log = new Logger(HadoopFsWrapper.class);

  private HadoopFsWrapper() {}

  /**
   * Same as FileSystem.rename(from, to, Options.Rename). It is different from FileSystem.rename(from, to) which moves
   * "from" directory inside "to" directory if it already exists.
   *
   * @param from
   * @param to
   * @param replaceExisting if existing files should be overwritten
   *
   * @return true if operation succeeded, false if replaceExisting == false and destination already exists
   *
   * @throws IOException if trying to overwrite a non-empty directory
   */
  public static boolean rename(FileSystem fs, Path from, Path to, boolean replaceExisting) throws IOException
  {
    try {
      fs.rename(from, to, replaceExisting ? Options.Rename.OVERWRITE : Options.Rename.NONE);
      return true;
    }
    catch (FileAlreadyExistsException ex) {
      log.info(ex, "Destination exists while renaming [%s] to [%s]", from, to);
      return false;
    }
  }
}

@@ -831,16 +831,58 @@
 op.fs;%0A%0A
+import com.google.common.base.Throwables;%0A
 import i
@@ -949,16 +949,101 @@
 ception;
+%0Aimport java.lang.reflect.InvocationTargetException;%0Aimport java.lang.reflect.Method;
 %0A%0A/**%0A *
@@ -2092,16 +2092,70 @@
  %7B%0A     
+ // Note: Using reflection instead of simpler%0A      //
  fs.rena
@@ -2244,38 +2244,355 @@
     
-return true;%0A    %7D%0A    catch (
+// due to the issues discussed in https://github.com/druid-io/druid/pull/3787%0A      Method renameMethod = findRenameMethodRecursively(fs.getClass());%0A      renameMethod.invoke(fs, from, to, new Options.Rename%5B%5D%7BOptions.Rename.NONE%7D);%0A      return true;%0A    %7D%0A    catch (InvocationTargetException ex) %7B%0A      if (ex.getTargetException() instanceof 
 File
@@ -2609,31 +2609,30 @@
 stsException
- ex
 ) %7B%0A
+  
       log.in
@@ -2701,24 +2701,26 @@
  to);%0A      
+  
 return false
@@ -2721,16 +2721,1124 @@
  false;%0A
+      %7D else %7B%0A        throw Throwables.propagate(ex);%0A      %7D%0A    %7D%0A    catch (NoSuchMethodException %7C IllegalAccessException ex) %7B%0A%0A      for (Method method : fs.getClass().getDeclaredMethods()) %7B%0A        log.error(method.toGenericString());%0A      %7D%0A      throw Throwables.propagate(ex);%0A    %7D%0A  %7D%0A%0A  /**%0A   * Finds %22rename%22 method recursively through the FileSystem class hierarchy. This is required because%0A   * clazz.getMethod(..) only returns PUBLIC methods in clazz hierarchy.%0A   * and clazz.getDeclaredMethod(..) only returns all methods declared in clazz but not inherited ones.%0A   */%0A  private static Method findRenameMethodRecursively(Class%3C?%3E clazz) throws NoSuchMethodException%0A  %7B%0A    try %7B%0A      Method renameMethod = clazz.getDeclaredMethod(%22rename%22, Path.class, Path.class, Options.Rename%5B%5D.class);%0A      renameMethod.setAccessible(true);%0A      return renameMethod;%0A    %7D%0A    catch (NoSuchMethodException ex) %7B%0A      Class%3C?%3E superClazz = clazz.getSuperclass();%0A      if (superClazz == null) %7B%0A        throw ex;%0A      %7D else %7B%0A        return findRenameMethodRecursively(superClazz);%0A      %7D%0A
     %7D%0A  

use reflection to call hadoop fs.rename to workaround different hadoop jar version in main and hdfs-storage extension class loader (#5296)