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  
18  package org.apache.commons.jci;
19  
20  import junit.framework.TestCase;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  
24  import org.apache.commons.jci.classes.SimpleDump;
25  import org.apache.commons.jci.stores.ResourceStore;
26  import org.apache.commons.jci.stores.MemoryResourceStore;
27  
28  /**
29   * Test ReloadingClassLoader's <code>removeResourceStore({@link ResourceStore})</code>
30   * method.
31   */
32  public class ReloadingClassLoaderRemoveTestCase extends TestCase {
33  
34      private final Log log = LogFactory.getLog(ReloadingClassLoaderRemoveTestCase.class);
35      
36      private final byte[] clazzSimpleA;
37      private MemoryResourceStore store1 = new MemoryResourceStore();
38      private MemoryResourceStore store2 = new MemoryResourceStore();
39      private MemoryResourceStore store3 = new MemoryResourceStore();
40      private MemoryResourceStore store4 = new MemoryResourceStore();
41  
42      public ReloadingClassLoaderRemoveTestCase() throws Exception {
43          clazzSimpleA = SimpleDump.dump("SimpleA");
44          assertTrue(clazzSimpleA.length > 0);
45      }
46  
47      protected void setUp() throws Exception {
48          System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
49      }
50      
51      protected void tearDown() throws Exception {
52      }
53  
54      /**
55       * Test trying to remove a ResourceStore from the ReloadingClassLoader
56       * which can't be found - when the ClassLoader contains NO other ResourceStore.
57       *
58       * Bug: The While loop in the removeResourceStore() throws an ArrayOutOfBoundsException
59       */
60      public void testRemoveStoreNotFoundClassLoaderNoStores() {
61          ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
62          checkRemoveResourceStore("No ResourceStore", loader, store1, false);
63      }
64  
65      /**
66       * Test trying to remove a ResourceStore from the ReloadingClassLoader
67       * which can't be found - when the ClassLoader DOES contain other ResourceStore.
68       *
69       * Bug: The While loop in the removeResourceStore() throws an ArrayOutOfBoundsException
70       */
71      public void testRemoveStoreNotFoundClassLoaderHasStores() {
72          ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
73          loader.addResourceStore(store1);
74          loader.addResourceStore(store2);
75          checkRemoveResourceStore("Has ResourceStore", loader, store3, false);
76      }
77  
78      /**
79       * Test trying to remove the first ResourceStore added
80       *
81       * Bug: ReloadingClassLoader addes ResourceStore at the start of the array. Removing the
82       *      first one added (last in array) causes the second System.arraycopy() statement to throw a
83       *      ArrayIndexOutOfBoundsException because the destination array position in the new smaller
84       *      array is too large.
85       */
86      public void testRemoveStoresOne() {
87          ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
88          loader.addResourceStore(store1);
89          loader.addResourceStore(store2);
90          loader.addResourceStore(store3);
91          loader.addResourceStore(store4);
92  
93          checkRemoveResourceStore("One: Remove Store 1", loader, store1, true);
94          checkRemoveResourceStore("One: Store 1 Not Found", loader, store1, false);
95  
96          checkRemoveResourceStore("One: Remove Store 2", loader, store2, true);
97          checkRemoveResourceStore("One: Store 2 Not Found", loader, store2, false);
98  
99          checkRemoveResourceStore("One: Remove Store 3", loader, store3, true);
100         checkRemoveResourceStore("One: Store 3 Not Found", loader, store3, false);
101 
102         checkRemoveResourceStore("One: Remove Store 4", loader, store4, true);
103         checkRemoveResourceStore("One: Store 4 Not Found", loader, store4, false);
104     }
105 
106     /**
107      * Test trying to remove the second ResourceStore added
108      *
109      * Bug: ReloadingClassLoader addes ResourceStore at the start of the array. Removing the
110      *      first one added (last in array) causes the second System.arraycopy() statement to throw a
111      *      ArrayIndexOutOfBoundsException (??not sure why??)
112      */
113     public void testRemoveStoresTwo() {
114         ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
115         loader.addResourceStore(store1);
116         loader.addResourceStore(store2);
117         loader.addResourceStore(store3);
118         loader.addResourceStore(store4);
119 
120         checkRemoveResourceStore("Two: Remove Store 2", loader, store2, true);
121         checkRemoveResourceStore("Two: Store 2 Not Found", loader, store2, false);
122 
123         checkRemoveResourceStore("Two: Remove Store 4", loader, store4, true);
124         checkRemoveResourceStore("Two: Store 4 Not Found", loader, store4, false);
125 
126         checkRemoveResourceStore("Two: Remove Store 3", loader, store3, true);
127         checkRemoveResourceStore("Two: Store 3 Not Found", loader, store3, false);
128 
129         checkRemoveResourceStore("Two: Remove Store 1", loader, store1, true);
130         checkRemoveResourceStore("Two: Store 1 Not Found", loader, store1, false);
131     }
132 
133     /**
134      * Test trying to remove the third ResourceStore added
135      *
136      * Bug: In this scenario the two System.arraycopy() statements don't copy the correct
137      *      ResourceStore - it creates a new array where the first resource store is null
138      *      and copies store3 and store2 to their same positions
139      */
140     public void testRemoveStoresThree() {
141         ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
142         loader.addResourceStore(store1);
143         loader.addResourceStore(store2);
144         loader.addResourceStore(store3);
145         loader.addResourceStore(store4);
146 
147         checkRemoveResourceStore("Three: Remove Store 3", loader, store3, true);
148         checkRemoveResourceStore("Three: Store 3 Not Found", loader, store3, false);
149 
150         checkRemoveResourceStore("Three: Remove Store 1", loader, store1, true);
151         checkRemoveResourceStore("Three: Store 1 Not Found", loader, store1, false);
152 
153         checkRemoveResourceStore("Three: Remove Store 4", loader, store4, true);
154         checkRemoveResourceStore("Three: Store 4 Not Found", loader, store4, false);
155 
156         checkRemoveResourceStore("Three: Remove Store 2", loader, store2, true);
157         checkRemoveResourceStore("Three: Store 2 Not Found", loader, store2, false);
158     }
159 
160     /**
161      * Test trying to remove the fourth ResourceStore added
162      *
163      * Bug: ReloadingClassLoader addes ResourceStore at the start of the array. Removing the
164      *      last one added (first in array) causes the first System.arraycopy() statement to throw a
165      *      ArrayIndexOutOfBoundsException because the length to copy is -1
166      */
167     public void testRemoveStoresFour() {
168         ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
169         loader.addResourceStore(store1);
170         loader.addResourceStore(store2);
171         loader.addResourceStore(store3);
172         loader.addResourceStore(store4);
173 
174         checkRemoveResourceStore("Four: Remove Store 4", loader, store4, true);
175         checkRemoveResourceStore("Four: Store 4 Not Found", loader, store4, false);
176 
177         checkRemoveResourceStore("Four: Remove Store 3", loader, store3, true);
178         checkRemoveResourceStore("Four: Store 3 Not Found", loader, store3, false);
179 
180         checkRemoveResourceStore("Four: Remove Store 2", loader, store2, true);
181         checkRemoveResourceStore("Four: Store 2 Not Found", loader, store2, false);
182 
183         checkRemoveResourceStore("Four: Remove Store 1", loader, store1, true);
184         checkRemoveResourceStore("Four: Store 1 Not Found", loader, store1, false);
185     }
186 
187 
188     /**
189      * Test that a class can't be loaded after the ResourceStore containing
190      * it has been removed.
191      *
192      * Bug: When theres a single ResourceStore in the ClassLoader and its removed
193      *      a new "delegate" ClassLoader with the new ResourceStore array isn't being
194      *      created - which means that calling loadClass() still returns the classes
195      *      from the removed ResourceStore rather than throwing a ClassNotFoundException
196      */
197     public void testLoadClassAfterResourceStoreRemoved() {
198 
199         // Create a class loader & add resource store
200         ReloadingClassLoader loader = new ReloadingClassLoader(this.getClass().getClassLoader());
201         MemoryResourceStore store = new MemoryResourceStore();
202         loader.addResourceStore(store);
203 
204         // Check "jci.Simple" class can't be loaded
205         try {
206             Object simple1 = loader.loadClass("jci.Simple").newInstance();        
207             fail("Success loadClass[1]");
208         } catch(ClassNotFoundException e) {
209             // expected not found
210         } catch(Exception e) {
211             log.error(e);
212             fail("Error loadClass[1]: " + e);
213         }
214 
215         // Add "jci.Simple" class to the resource store
216         String toStringValue = "FooBar";
217         try {
218             byte[] classBytes = SimpleDump.dump(toStringValue);
219             store.write("jci/Simple.class", classBytes);
220         } catch(Exception e) {
221             log.error(e);
222             fail("Error adding class to store: " + e);
223         }
224 
225         // Check "jci.Simple" class can now be loaded
226         try {
227             Object simple2 = loader.loadClass("jci.Simple").newInstance();        
228             assertNotNull("Found loadClass[2]",  simple2);        
229             assertEquals("toString loadClass[2]",  toStringValue, simple2.toString());        
230         } catch(Exception e) {
231             log.error(e);
232             fail("Error loadClass[2]: " + e);
233         }
234 
235         // Remove the resource store from the class loader
236         checkRemoveResourceStore("Remove Resource Store", loader, store, true);
237 
238         // Test "jci.Simple" class can't be loaded after ResourceStore removed
239         try {
240             Object simple3 = loader.loadClass("jci.Simple").newInstance();        
241             fail("Success loadClass[3]");
242         } catch(ClassNotFoundException e) {
243             // expected not found
244         } catch(Exception e) {
245             log.error(e);
246             fail("Error loadClass[3]: " + e);
247         }
248 
249     }
250 
251     /**
252      * Check removing a ResourceStore from ReloadingClassLoader
253      */
254     private void checkRemoveResourceStore(String label, ReloadingClassLoader loader, ResourceStore store, boolean expected) {
255         try {
256             assertEquals(label, expected, loader.removeResourceStore(store));
257         } catch(Exception e) {
258             log.error(label, e);
259             fail(label + " failed: " + e);
260         }
261     }
262 }