View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.impl;
18  
19  import static org.apache.commons.vfs2.VfsTestUtils.getTestDirectoryFile;
20  
21  import java.io.File;
22  import java.io.PrintWriter;
23  import java.net.URL;
24  import java.net.URLConnection;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.Enumeration;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Queue;
31  import java.util.concurrent.ArrayBlockingQueue;
32  import java.util.concurrent.BlockingQueue;
33  import java.util.concurrent.RejectedExecutionHandler;
34  import java.util.concurrent.ThreadFactory;
35  import java.util.concurrent.ThreadPoolExecutor;
36  import java.util.concurrent.TimeUnit;
37  
38  import org.apache.commons.io.output.StringBuilderWriter;
39  import org.apache.commons.vfs2.AbstractProviderTestCase;
40  import org.apache.commons.vfs2.Capability;
41  import org.apache.commons.vfs2.FileObject;
42  import org.apache.commons.vfs2.FileSystemException;
43  import org.apache.commons.vfs2.FileSystemManager;
44  import org.apache.commons.vfs2.FileType;
45  import org.junit.Test;
46  
47  /**
48   * VfsClassLoader test cases.
49   */
50  public class VfsClassLoaderTests extends AbstractProviderTestCase {
51  
52      private class LoadClass implements Runnable {
53          private final VFSClassLoader loader;
54          public LoadClass(final VFSClassLoader loader) {
55              this.loader = loader;
56          }
57  
58          @Override
59          public void run() {
60              try {
61                  final Class<?> testClass = loader.findClass("code.ClassToLoad");
62                  final Package pack = testClass.getPackage();
63                  assertEquals("code", pack.getName());
64                  verifyPackage(pack, false);
65  
66                  final Object testObject = testClass.getConstructor().newInstance();
67                  assertEquals("**PRIVATE**", testObject.toString());
68              } catch (final ReflectiveOperationException e) {
69                  throw new IllegalStateException(e);
70              }
71          }
72      }
73  
74      /**
75       * Non-Delegating Class Loader.
76       */
77      public static class MockClassloader extends ClassLoader {
78          MockClassloader() {
79              super(null);
80          }
81  
82          @Override
83          protected Class<?> findClass(final String name) throws ClassNotFoundException {
84              fail("Not intended to be used for class loading.");
85              return null;
86          }
87  
88          /**
89           * This method will not return any hit to VFSClassLoader#testGetResourcesJARs.
90           */
91          @Override
92          public Enumeration<URL> getResources(final String name) {
93              return Collections.enumeration(Collections.emptyList());
94          }
95      }
96  
97      /**
98       * Creates the classloader to use when testing.
99       */
100     private VFSClassLoader createClassLoader() throws FileSystemException {
101         return new VFSClassLoader(getBaseFolder(), getManager());
102     }
103 
104     /**
105      * Returns the capabilities required by the tests of this test case.
106      */
107     @Override
108     protected Capability[] getRequiredCapabilities() {
109         return new Capability[] { Capability.READ_CONTENT, Capability.URI };
110     }
111 
112     /**
113      * Tests retrieving resources (from JAR searchpath).
114      * <p>
115      * This is run for all providers, but only when a local provider is present and jar extension is registered it will
116      * actually carry out all tests.
117      * </p>
118      */
119     @Test
120     public void testGetResourcesJARs() throws Exception {
121         final FileSystemManager manager = getManager();
122         try {
123             // hasProvider("file") cannot be used as it triggers default provider URL
124             manager.toFileObject(new File("."));
125         } catch (final FileSystemException e) {
126             System.out.println("VfsClassLoaderTests no local file provider, skipping.");
127             return;
128         }
129 
130         // build search path without using #getBaseFolder()
131         // because NestedJarTestCase redefines it
132         final File baseDir = getTestDirectoryFile();
133         final FileObject nestedJar = manager.resolveFile(baseDir, "nested.jar");
134         final FileObject testJar = manager.resolveFile(baseDir, "test.jar");
135 
136         // test setup needs to know about .jar extension - i.e. NestedJarTestCase
137         if (!manager.canCreateFileSystem(nestedJar)) {
138             System.out.println("VfsClassLoaderTests no layered .jar provider, skipping.");
139             return;
140         }
141 
142         // verify test setup
143         assertSame("nested.jar is required for testing", nestedJar.getType(), FileType.FILE);
144         assertSame("test.jar is required for testing", testJar.getType(), FileType.FILE);
145 
146         // System class loader (null) might be unpredictable in regards
147         // to returning resources for META-INF/MANIFEST.MF (see VFS-500)
148         // so we use our own which is guaranteed to not return any hit
149         final ClassLoader mockClassloader = new MockClassloader();
150         final FileObject[] search = { nestedJar, testJar };
151         final VFSClassLoader loader = new VFSClassLoader(search, getManager(), mockClassloader);
152 
153         final Enumeration<URL> urls = loader.getResources("META-INF/MANIFEST.MF");
154         final URL url1 = urls.nextElement();
155         final URL url2 = urls.nextElement();
156 
157         assertTrue("First resource must refer to nested.jar but was " + url1,
158                 url1.toString().endsWith("nested.jar!/META-INF/MANIFEST.MF"));
159         assertTrue("Second resource must refer to test.jar but was " + url2,
160                 url2.toString().endsWith("test.jar!/META-INF/MANIFEST.MF"));
161     }
162 
163     /**
164      * Tests retrieving resources (from local directory with .jar extension).
165      * <p>
166      * This test is repeated with various provider configurations but works on local files, only.
167      * </p>
168      */
169     @Test
170     public void testGetResourcesNoLayerLocal() throws Exception {
171         final FileSystemManager manager = getManager();
172         try {
173             // hasProvider("file") cannot be used as it triggers default provider URL
174             manager.toFileObject(new File("."));
175         } catch (final FileSystemException e) {
176             System.out.println("VfsClassLoaderTests no local file provider, skipping.");
177             return;
178         }
179         final File baseDir = getTestDirectoryFile();
180 
181         // setup test folder
182         final FileObject dir = manager.resolveFile(baseDir, "read-tests/dir1/subdir4.jar");
183         assertSame("subdir4.jar/ is required for testing " + dir, dir.getType(), FileType.FOLDER);
184         assertFalse(manager.canCreateFileSystem(dir));
185 
186         // prepare classloader
187         final FileObject[] search = { dir };
188         final ClassLoader mockClassloader = new MockClassloader();
189         final VFSClassLoader loader = new VFSClassLoader(search, getManager(), mockClassloader);
190 
191         // verify resource loading
192         final Enumeration<URL> urls = loader.getResources("file1.txt");
193         final URL url1 = urls.nextElement();
194         assertFalse("Only one hit expected", urls.hasMoreElements());
195         assertTrue("not pointing to resource " + url1, url1.toString().endsWith("subdir4.jar/file1.txt"));
196     }
197 
198     /**
199      * Tests loading a class.
200      */
201     @Test
202     public void testLoadClass() throws Exception {
203         final VFSClassLoader loader = createClassLoader();
204 
205         final Class<?> testClass = loader.loadClass("code.ClassToLoad");
206         final Package pack = testClass.getPackage();
207         assertEquals("code", pack.getName());
208         verifyPackage(pack, false);
209 
210         final Object testObject = testClass.getConstructor().newInstance();
211         assertEquals("**PRIVATE**", testObject.toString());
212     }
213 
214     /**
215      * Tests loading a resource.
216      */
217     @Test
218     public void testLoadResource() throws Exception {
219         final VFSClassLoader loader = createClassLoader();
220 
221         final URL resource = loader.getResource("read-tests/file1.txt");
222 
223         assertNotNull(resource);
224         final URLConnection urlCon = resource.openConnection();
225         assertSameURLContent(FILE1_CONTENT, urlCon);
226     }
227 
228     /**
229      * Tests package sealing.
230      */
231     @Test
232     public void testSealing() throws Exception {
233         final VFSClassLoader loader = createClassLoader();
234         final Class<?> testClass = loader.loadClass("code.sealed.AnotherClass");
235         final Package pack = testClass.getPackage();
236         assertEquals("code.sealed", pack.getName());
237         verifyPackage(pack, true);
238     }
239 
240     @Test
241     public void testThreadSafety() throws Exception {
242         final int THREADS = 40;
243         final BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(THREADS * 2);
244         final List<Throwable> exceptions = new ArrayList<>();
245         final Thread.UncaughtExceptionHandler handler = (t, e) -> {
246             synchronized (exceptions) {
247                 exceptions.add(e);
248             }
249         };
250         final ThreadFactory factory = r -> {
251             final Thread thread = new Thread(r, "VfsClassLoaderTests.testThreadSafety");
252             thread.setUncaughtExceptionHandler(handler);
253             return thread;
254         };
255         final Queue<Runnable> rejections = new LinkedList<>();
256         final RejectedExecutionHandler rejectionHandler = (r, executor) -> {
257             synchronized (rejections) {
258                 rejections.add(r);
259             }
260         };
261         final ThreadPoolExecutor executor = new ThreadPoolExecutor(THREADS, THREADS, 0, TimeUnit.SECONDS, workQueue, factory, rejectionHandler);
262         executor.prestartAllCoreThreads();
263         for (int i = 0; i < THREADS; i++) {
264             final VFSClassLoader loader = createClassLoader();
265             workQueue.put(new VfsClassLoaderTests.LoadClass(loader));
266         }
267         while (!workQueue.isEmpty()) {
268             Thread.sleep(10);
269         }
270         while (!rejections.isEmpty() && executor.getActiveCount() > 0) {
271             final List<Runnable> rejected = new ArrayList<>();
272             synchronized(rejections) {
273                 rejected.addAll(rejections);
274                 rejections.clear();
275             }
276             workQueue.addAll(rejected);
277         }
278         executor.shutdown();
279         executor.awaitTermination(30, TimeUnit.SECONDS);
280         assertEquals(THREADS, executor.getCompletedTaskCount());
281         if (!exceptions.isEmpty()) {
282             final StringBuilder exceptionMsg = new StringBuilder();
283             final StringBuilderWriter writer = new StringBuilderWriter(exceptionMsg);
284             final PrintWriter pWriter = new PrintWriter(writer);
285             for (final Throwable t : exceptions) {
286                 pWriter.write(t.getMessage());
287                 pWriter.write('\n');
288                 t.printStackTrace(pWriter);
289                 pWriter.write('\n');
290             }
291             pWriter.flush();
292             assertTrue(exceptions.size() + " threads failed: " + exceptionMsg, exceptions.isEmpty());
293         }
294     }
295 
296     /**
297      * Verify the package loaded with class loader.
298      */
299     private void verifyPackage(final Package pack, final boolean sealed) {
300         if (getBaseFolder().getFileSystem().hasCapability(Capability.MANIFEST_ATTRIBUTES)) {
301             assertEquals("ImplTitle", pack.getImplementationTitle());
302             assertEquals("ImplVendor", pack.getImplementationVendor());
303             assertEquals("1.1", pack.getImplementationVersion());
304             assertEquals("SpecTitle", pack.getSpecificationTitle());
305             assertEquals("SpecVendor", pack.getSpecificationVendor());
306             assertEquals("1.0", pack.getSpecificationVersion());
307             assertEquals(sealed, pack.isSealed());
308         } else {
309             assertNull(pack.getImplementationTitle());
310             assertNull(pack.getImplementationVendor());
311             assertNull(pack.getImplementationVersion());
312             assertNull(pack.getSpecificationTitle());
313             assertNull(pack.getSpecificationVendor());
314             assertNull(pack.getSpecificationVersion());
315             assertFalse(pack.isSealed());
316         }
317     }
318 
319 }