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.geronimo.kernel.rmi;
019    
020    import java.net.MalformedURLException;
021    import java.net.URL;
022    
023    import java.io.File;
024    
025    import java.util.StringTokenizer;
026    
027    import java.rmi.server.RMIClassLoader;
028    import java.rmi.server.RMIClassLoaderSpi;
029    
030    import java.util.concurrent.ConcurrentHashMap;
031    
032    /**
033     * An implementation of {@link RMIClassLoaderSpi} which provides normilzation
034     * of codebase URLs and delegates to the default {@link RMIClassLoaderSpi}.
035     *
036     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
037     */
038    public class RMIClassLoaderSpiImpl
039        extends RMIClassLoaderSpi
040    {
041        private RMIClassLoaderSpi delegate = RMIClassLoader.getDefaultProviderInstance();
042    
043        //TODO: Not sure of the best initial size.  Starting with 100 which should be reasonable.
044        private ConcurrentHashMap cachedCodebases = new ConcurrentHashMap(100);
045    
046        public Class loadClass(String codebase, String name, ClassLoader defaultLoader)
047            throws MalformedURLException, ClassNotFoundException
048        {
049            if (codebase != null) {
050                codebase = getNormalizedCodebase(codebase);
051            }
052            
053            return delegate.loadClass(codebase, name, defaultLoader);
054        }
055        
056        public Class loadProxyClass(String codebase, String[] interfaces, ClassLoader defaultLoader)
057            throws MalformedURLException, ClassNotFoundException
058        {
059            if (codebase != null) {
060                codebase = getNormalizedCodebase(codebase);
061            }
062            
063            return delegate.loadProxyClass(codebase, interfaces, defaultLoader);
064        }
065        
066        public ClassLoader getClassLoader(String codebase)
067            throws MalformedURLException
068        {
069            if (codebase != null) {
070                codebase = getNormalizedCodebase(codebase);
071            }
072            
073            return delegate.getClassLoader(codebase);
074        }
075        
076        public String getClassAnnotation(Class type) {
077            Object obj = type.getClassLoader();
078            if (obj instanceof ClassLoaderServerAware) {
079                ClassLoaderServerAware classLoader = (ClassLoaderServerAware) obj;
080                URL urls[] = classLoader.getClassLoaderServerURLs();
081                if (null == urls) {
082                    return delegate.getClassAnnotation(type);
083                }
084                StringBuffer codebase = new StringBuffer();
085                for (int i = 0; i < urls.length; i++) {
086                    URL url = normalizeURL(urls[i]);
087                    if (codebase.length() != 0) {
088                        codebase.append(' ');
089                    }
090                    codebase.append(url);
091                }
092                return codebase.toString();
093            }
094            
095            String codebase = delegate.getClassAnnotation(type);
096            if (codebase != null) {
097                try {
098                    codebase = getNormalizedCodebase( codebase );
099                }
100                catch (MalformedURLException ignore) {
101                }
102            }
103            return codebase;
104        }
105    
106        /**
107         * Uses a ConcurrentReaderHashmap to save the contents of previous parses.
108         *
109         * @param codebase
110         * @return
111         * @throws MalformedURLException
112         */
113        private String getNormalizedCodebase(String codebase)
114                throws MalformedURLException {
115            String cachedCodebase = (String)cachedCodebases.get(codebase);
116            if (cachedCodebase != null)
117                return cachedCodebase;
118    
119            String normalizedCodebase = normalizeCodebase(codebase);
120            String oldValue = (String)cachedCodebases.put(codebase, normalizedCodebase);
121    
122            // If there was a previous value remove the one we just added to make sure the
123            // cache doesn't grow.
124            if (oldValue != null) {
125                cachedCodebases.remove(codebase);
126            }
127            return normalizedCodebase;  // We can use the oldValue
128        }
129    
130    
131        static String normalizeCodebase(String input)
132            throws MalformedURLException
133        {
134            assert input != null;
135    
136            StringBuffer codebase = new StringBuffer();
137            StringBuffer working = new StringBuffer();
138            StringTokenizer stok = new StringTokenizer(input, " \t\n\r\f", true);
139            
140            while (stok.hasMoreTokens()) {
141                String item = stok.nextToken();
142                // Optimisation: This optimisation to prevent unnecessary MalformedURLExceptions 
143                //   being generated is most helpful on windows where directory names in the path 
144                //   often contain spaces.  E.G:
145                //     file:/C:/Program Files/Apache Software Foundation/Maven 1.0.2/lib/ant-1.5.3-1.jar
146                //
147                //   Therefore we won't attempt URL("Files/Apache) or URL(" ") for the path delimiter.
148                if ( item.indexOf(':') != -1 )
149                {
150                    try {
151                        URL url = new URL(item);
152                        // If we got this far then item is a valid url, so commit the current
153                        // buffer and start collecting any trailing bits from where we are now
154                        updateCodebase(working, codebase);
155                    } catch (MalformedURLException ignore) {
156                        // just keep going & append to the working buffer
157                    }
158                }
159                
160                // Append the URL or delimiter to the working buffer
161                working.append(item);
162            }
163            
164            // Handle trailing elements
165            updateCodebase(working, codebase);
166            
167            // System.out.println("Normalized codebase: " + codebase);
168            return codebase.toString();
169        }
170        
171        private static void updateCodebase(final StringBuffer working, final StringBuffer codebase)
172            throws MalformedURLException
173        {
174            if (working.length() != 0) {
175                // Normalize the URL
176                URL url = normalizeURL(new URL(working.toString()));
177                // System.out.println("Created normalized URL: " + url);
178                
179                // Put spaces back in for URL delims
180                if (codebase.length() != 0) {
181                    codebase.append(" ");
182                }
183                codebase.append(url);
184                
185                // Reset the working buffer
186                working.setLength(0);
187            }
188        }
189        
190        static URL normalizeURL(URL url)
191        {
192            assert url != null;
193            
194            if (url.getProtocol().equals("file")) {
195                String filename = url.getFile().replace('/', File.separatorChar);
196                File file = new File(filename);
197                try {
198                    url = file.toURI().toURL();
199                }
200                catch (MalformedURLException ignore) {}
201            }
202            
203            return url;
204        }
205        
206        public interface ClassLoaderServerAware {
207            public URL[] getClassLoaderServerURLs();
208        }
209    }