View Javadoc
1   /*
2    *    Copyright 2010-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.migration.io;
17  
18  import java.io.IOException;
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.net.URL;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.logging.Level;
27  import java.util.logging.Logger;
28  
29  /**
30   * Provides a very simple API for accessing resources within an application server.
31   *
32   * @author Ben Gunter
33   */
34  public abstract class VFS {
35    private static final Logger log = Logger.getLogger(VFS.class.getName());
36  
37    /** The built-in implementations. */
38    protected static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };
39  
40    /**
41     * The list to which implementations are added by {@link #addImplClass(Class)}.
42     */
43    protected static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<>();
44  
45    /** Singleton instance holder. */
46    private static class VFSHolder {
47      static final VFS INSTANCE = createVFS();
48  
49      @SuppressWarnings("unchecked")
50      static VFS createVFS() {
51        // Try the user implementations first, then the built-ins
52        List<Class<? extends VFS>> impls = new ArrayList<>(USER_IMPLEMENTATIONS);
53        impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
54  
55        // Try each implementation class until a valid one is found
56        VFS vfs = null;
57        for (int i = 0; vfs == null || !vfs.isValid(); i++) {
58          Class<? extends VFS> impl = impls.get(i);
59          try {
60            vfs = impl.getDeclaredConstructor().newInstance();
61            if (!vfs.isValid() && log.isLoggable(Level.FINER)) {
62              log.log(Level.FINER, "VFS implementation " + impl.getName() + " is not valid in this environment.");
63            }
64          } catch (InstantiationException | IllegalAccessException | NoSuchMethodException
65              | InvocationTargetException e) {
66            log.log(Level.SEVERE, "Failed to instantiate " + impl, e);
67            return null;
68          }
69        }
70  
71        if (log.isLoggable(Level.FINER)) {
72          log.log(Level.FINER, "Using VFS adapter " + vfs.getClass().getName());
73        }
74  
75        return vfs;
76      }
77    }
78  
79    /**
80     * Get the singleton {@link VFS} instance. If no {@link VFS} implementation can be found for the current environment,
81     * then this method returns null.
82     *
83     * @return single instance of VFS
84     */
85    public static VFS getInstance() {
86      return VFSHolder.INSTANCE;
87    }
88  
89    /**
90     * Adds the specified class to the list of {@link VFS} implementations. Classes added in this manner are tried in the
91     * order they are added and before any of the built-in implementations.
92     *
93     * @param clazz
94     *          The {@link VFS} implementation class to add.
95     */
96    public static void addImplClass(Class<? extends VFS> clazz) {
97      if (clazz != null) {
98        USER_IMPLEMENTATIONS.add(clazz);
99      }
100   }
101 
102   /**
103    * Get a class by name. If the class is not found then return null.
104    *
105    * @param className
106    *          the class name
107    *
108    * @return the class
109    */
110   protected static Class<?> getClass(String className) {
111     try {
112       return Thread.currentThread().getContextClassLoader().loadClass(className);
113       // return ReflectUtil.findClass(className);
114     } catch (ClassNotFoundException e) {
115       if (log.isLoggable(Level.FINER)) {
116         log.log(Level.FINER, "Class not found: " + className);
117       }
118       return null;
119     }
120   }
121 
122   /**
123    * Get a method by name and parameter types. If the method is not found then return null.
124    *
125    * @param clazz
126    *          The class to which the method belongs.
127    * @param methodName
128    *          The name of the method.
129    * @param parameterTypes
130    *          The types of the parameters accepted by the method.
131    *
132    * @return the method
133    */
134   protected static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
135     if (clazz == null) {
136       return null;
137     }
138     try {
139       return clazz.getMethod(methodName, parameterTypes);
140     } catch (SecurityException e) {
141       log.log(Level.SEVERE,
142           "Security exception looking for method " + clazz.getName() + "." + methodName + ".  Cause: " + e);
143       return null;
144     } catch (NoSuchMethodException e) {
145       log.log(Level.SEVERE,
146           "Method not found " + clazz.getName() + "." + methodName + "." + methodName + ".  Cause: " + e);
147       return null;
148     }
149   }
150 
151   /**
152    * Invoke a method on an object and return whatever it returns.
153    *
154    * @param <T>
155    *          the generic type
156    * @param method
157    *          The method to invoke.
158    * @param object
159    *          The instance or class (for static methods) on which to invoke the method.
160    * @param parameters
161    *          The parameters to pass to the method.
162    *
163    * @return Whatever the method returns.
164    *
165    * @throws IOException
166    *           If I/O errors occur
167    * @throws RuntimeException
168    *           If anything else goes wrong
169    */
170   @SuppressWarnings("unchecked")
171   protected static <T> T invoke(Method method, Object object, Object... parameters)
172       throws IOException, RuntimeException {
173     try {
174       return (T) method.invoke(object, parameters);
175     } catch (IllegalArgumentException | IllegalAccessException e) {
176       throw new RuntimeException(e);
177     } catch (InvocationTargetException e) {
178       if (e.getTargetException() instanceof IOException) {
179         throw (IOException) e.getTargetException();
180       }
181       throw new RuntimeException(e);
182     }
183   }
184 
185   /**
186    * Get a list of {@link URL}s from the context classloader for all the resources found at the specified path.
187    *
188    * @param path
189    *          The resource path.
190    *
191    * @return A list of {@link URL}s, as returned by {@link ClassLoader#getResources(String)}.
192    *
193    * @throws IOException
194    *           If I/O errors occur
195    */
196   protected static List<URL> getResources(String path) throws IOException {
197     return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
198   }
199 
200   /**
201    * Return true if the {@link VFS} implementation is valid for the current environment.
202    *
203    * @return true, if is valid
204    */
205   public abstract boolean isValid();
206 
207   /**
208    * Recursively list the full resource path of all the resources that are children of the resource identified by a URL.
209    *
210    * @param url
211    *          The URL that identifies the resource to list.
212    * @param forPath
213    *          The path to the resource that is identified by the URL. Generally, this is the value passed to
214    *          {@link #getResources(String)} to get the resource URL.
215    *
216    * @return A list containing the names of the child resources.
217    *
218    * @throws IOException
219    *           If I/O errors occur
220    */
221   protected abstract List<String> list(URL url, String forPath) throws IOException;
222 
223   /**
224    * Recursively list the full resource path of all the resources that are children of all the resources found at the
225    * specified path.
226    *
227    * @param path
228    *          The path of the resource(s) to list.
229    *
230    * @return A list containing the names of the child resources.
231    *
232    * @throws IOException
233    *           If I/O errors occur
234    */
235   public List<String> list(String path) throws IOException {
236     List<String> names = new ArrayList<>();
237     for (URL url : getResources(path)) {
238       names.addAll(list(url, path));
239     }
240     return names;
241   }
242 }