001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.jci;
019    
020    import junit.framework.TestCase;
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    
024    import org.apache.commons.jci.classes.SimpleDump;
025    import org.apache.commons.jci.stores.ResourceStore;
026    import org.apache.commons.jci.stores.MemoryResourceStore;
027    
028    /**
029     * Test ReloadingClassLoader's <code>removeResourceStore({@link ResourceStore})</code>
030     * method.
031     */
032    public class ReloadingClassLoaderRemoveTestCase extends TestCase {
033    
034        private final Log log = LogFactory.getLog(ReloadingClassLoaderRemoveTestCase.class);
035        
036        private final byte[] clazzSimpleA;
037        private MemoryResourceStore store1 = new MemoryResourceStore();
038        private MemoryResourceStore store2 = new MemoryResourceStore();
039        private MemoryResourceStore store3 = new MemoryResourceStore();
040        private MemoryResourceStore store4 = new MemoryResourceStore();
041    
042        public ReloadingClassLoaderRemoveTestCase() throws Exception {
043            clazzSimpleA = SimpleDump.dump("SimpleA");
044            assertTrue(clazzSimpleA.length > 0);
045        }
046    
047        @Override
048        protected void setUp() throws Exception {
049            System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
050        }
051        
052        @Override
053        protected void tearDown() throws Exception {
054        }
055    
056        /**
057         * Test trying to remove a ResourceStore from the ReloadingClassLoader
058         * which can't be found - when the ClassLoader contains NO other ResourceStore.
059         *
060         * Bug: The While loop in the removeResourceStore() throws an ArrayOutOfBoundsException
061         */
062        public void testRemoveStoreNotFoundClassLoaderNoStores() {
063            ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
064            checkRemoveResourceStore("No ResourceStore", loader, store1, false);
065        }
066    
067        /**
068         * Test trying to remove a ResourceStore from the ReloadingClassLoader
069         * which can't be found - when the ClassLoader DOES contain other ResourceStore.
070         *
071         * Bug: The While loop in the removeResourceStore() throws an ArrayOutOfBoundsException
072         */
073        public void testRemoveStoreNotFoundClassLoaderHasStores() {
074            ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
075            loader.addResourceStore(store1);
076            loader.addResourceStore(store2);
077            checkRemoveResourceStore("Has ResourceStore", loader, store3, false);
078        }
079    
080        /**
081         * Test trying to remove the first ResourceStore added
082         *
083         * Bug: ReloadingClassLoader addes ResourceStore at the start of the array. Removing the
084         *      first one added (last in array) causes the second System.arraycopy() statement to throw a
085         *      ArrayIndexOutOfBoundsException because the destination array position in the new smaller
086         *      array is too large.
087         */
088        public void testRemoveStoresOne() {
089            ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
090            loader.addResourceStore(store1);
091            loader.addResourceStore(store2);
092            loader.addResourceStore(store3);
093            loader.addResourceStore(store4);
094    
095            checkRemoveResourceStore("One: Remove Store 1", loader, store1, true);
096            checkRemoveResourceStore("One: Store 1 Not Found", loader, store1, false);
097    
098            checkRemoveResourceStore("One: Remove Store 2", loader, store2, true);
099            checkRemoveResourceStore("One: Store 2 Not Found", loader, store2, false);
100    
101            checkRemoveResourceStore("One: Remove Store 3", loader, store3, true);
102            checkRemoveResourceStore("One: Store 3 Not Found", loader, store3, false);
103    
104            checkRemoveResourceStore("One: Remove Store 4", loader, store4, true);
105            checkRemoveResourceStore("One: Store 4 Not Found", loader, store4, false);
106        }
107    
108        /**
109         * Test trying to remove the second ResourceStore added
110         *
111         * Bug: ReloadingClassLoader addes ResourceStore at the start of the array. Removing the
112         *      first one added (last in array) causes the second System.arraycopy() statement to throw a
113         *      ArrayIndexOutOfBoundsException (??not sure why??)
114         */
115        public void testRemoveStoresTwo() {
116            ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
117            loader.addResourceStore(store1);
118            loader.addResourceStore(store2);
119            loader.addResourceStore(store3);
120            loader.addResourceStore(store4);
121    
122            checkRemoveResourceStore("Two: Remove Store 2", loader, store2, true);
123            checkRemoveResourceStore("Two: Store 2 Not Found", loader, store2, false);
124    
125            checkRemoveResourceStore("Two: Remove Store 4", loader, store4, true);
126            checkRemoveResourceStore("Two: Store 4 Not Found", loader, store4, false);
127    
128            checkRemoveResourceStore("Two: Remove Store 3", loader, store3, true);
129            checkRemoveResourceStore("Two: Store 3 Not Found", loader, store3, false);
130    
131            checkRemoveResourceStore("Two: Remove Store 1", loader, store1, true);
132            checkRemoveResourceStore("Two: Store 1 Not Found", loader, store1, false);
133        }
134    
135        /**
136         * Test trying to remove the third ResourceStore added
137         *
138         * Bug: In this scenario the two System.arraycopy() statements don't copy the correct
139         *      ResourceStore - it creates a new array where the first resource store is null
140         *      and copies store3 and store2 to their same positions
141         */
142        public void testRemoveStoresThree() {
143            ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
144            loader.addResourceStore(store1);
145            loader.addResourceStore(store2);
146            loader.addResourceStore(store3);
147            loader.addResourceStore(store4);
148    
149            checkRemoveResourceStore("Three: Remove Store 3", loader, store3, true);
150            checkRemoveResourceStore("Three: Store 3 Not Found", loader, store3, false);
151    
152            checkRemoveResourceStore("Three: Remove Store 1", loader, store1, true);
153            checkRemoveResourceStore("Three: Store 1 Not Found", loader, store1, false);
154    
155            checkRemoveResourceStore("Three: Remove Store 4", loader, store4, true);
156            checkRemoveResourceStore("Three: Store 4 Not Found", loader, store4, false);
157    
158            checkRemoveResourceStore("Three: Remove Store 2", loader, store2, true);
159            checkRemoveResourceStore("Three: Store 2 Not Found", loader, store2, false);
160        }
161    
162        /**
163         * Test trying to remove the fourth ResourceStore added
164         *
165         * Bug: ReloadingClassLoader addes ResourceStore at the start of the array. Removing the
166         *      last one added (first in array) causes the first System.arraycopy() statement to throw a
167         *      ArrayIndexOutOfBoundsException because the length to copy is -1
168         */
169        public void testRemoveStoresFour() {
170            ReloadingClassLoader loader = new ReloadingClassLoader(getClass().getClassLoader());
171            loader.addResourceStore(store1);
172            loader.addResourceStore(store2);
173            loader.addResourceStore(store3);
174            loader.addResourceStore(store4);
175    
176            checkRemoveResourceStore("Four: Remove Store 4", loader, store4, true);
177            checkRemoveResourceStore("Four: Store 4 Not Found", loader, store4, false);
178    
179            checkRemoveResourceStore("Four: Remove Store 3", loader, store3, true);
180            checkRemoveResourceStore("Four: Store 3 Not Found", loader, store3, false);
181    
182            checkRemoveResourceStore("Four: Remove Store 2", loader, store2, true);
183            checkRemoveResourceStore("Four: Store 2 Not Found", loader, store2, false);
184    
185            checkRemoveResourceStore("Four: Remove Store 1", loader, store1, true);
186            checkRemoveResourceStore("Four: Store 1 Not Found", loader, store1, false);
187        }
188    
189    
190        /**
191         * Test that a class can't be loaded after the ResourceStore containing
192         * it has been removed.
193         *
194         * Bug: When theres a single ResourceStore in the ClassLoader and its removed
195         *      a new "delegate" ClassLoader with the new ResourceStore array isn't being
196         *      created - which means that calling loadClass() still returns the classes
197         *      from the removed ResourceStore rather than throwing a ClassNotFoundException
198         */
199        public void testLoadClassAfterResourceStoreRemoved() {
200    
201            // Create a class loader & add resource store
202            ReloadingClassLoader loader = new ReloadingClassLoader(this.getClass().getClassLoader());
203            MemoryResourceStore store = new MemoryResourceStore();
204            loader.addResourceStore(store);
205    
206            // Check "jci.Simple" class can't be loaded
207            try {
208                loader.loadClass("jci.Simple").newInstance();        
209                fail("Success loadClass[1]");
210            } catch(ClassNotFoundException e) {
211                // expected not found
212            } catch(Exception e) {
213                log.error(e);
214                fail("Error loadClass[1]: " + e);
215            }
216    
217            // Add "jci.Simple" class to the resource store
218            String toStringValue = "FooBar";
219            try {
220                byte[] classBytes = SimpleDump.dump(toStringValue);
221                store.write("jci/Simple.class", classBytes);
222            } catch(Exception e) {
223                log.error(e);
224                fail("Error adding class to store: " + e);
225            }
226    
227            // Check "jci.Simple" class can now be loaded
228            try {
229                Object simple2 = loader.loadClass("jci.Simple").newInstance();        
230                assertNotNull("Found loadClass[2]",  simple2);        
231                assertEquals("toString loadClass[2]",  toStringValue, simple2.toString());        
232            } catch(Exception e) {
233                log.error(e);
234                fail("Error loadClass[2]: " + e);
235            }
236    
237            // Remove the resource store from the class loader
238            checkRemoveResourceStore("Remove Resource Store", loader, store, true);
239    
240            // Test "jci.Simple" class can't be loaded after ResourceStore removed
241            try {
242                loader.loadClass("jci.Simple").newInstance();        
243                fail("Success loadClass[3]");
244            } catch(ClassNotFoundException e) {
245                // expected not found
246            } catch(Exception e) {
247                log.error(e);
248                fail("Error loadClass[3]: " + e);
249            }
250    
251        }
252    
253        /**
254         * Check removing a ResourceStore from ReloadingClassLoader
255         */
256        private void checkRemoveResourceStore(String label, ReloadingClassLoader loader, ResourceStore store, boolean expected) {
257            try {
258                assertEquals(label, expected, loader.removeResourceStore(store));
259            } catch(Exception e) {
260                log.error(label, e);
261                fail(label + " failed: " + e);
262            }
263        }
264    }