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: 520573 $ $Date: 2007-03-20 16:48:26 -0400 (Tue, 20 Mar 2007) $
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            return delegate.getClassAnnotation(type);
096        }
097    
098        /**
099         * Uses a ConcurrentReaderHashmap to save the contents of previous parses.
100         *
101         * @param codebase
102         * @return
103         * @throws MalformedURLException
104         */
105        private String getNormalizedCodebase(String codebase)
106                throws MalformedURLException {
107            String cachedCodebase = (String)cachedCodebases.get(codebase);
108            if (cachedCodebase != null)
109                return cachedCodebase;
110    
111            String normalizedCodebase = normalizeCodebase(codebase);
112            String oldValue = (String)cachedCodebases.put(codebase, normalizedCodebase);
113    
114            // If there was a previous value remove the one we just added to make sure the
115            // cache doesn't grow.
116            if (oldValue != null) {
117                cachedCodebases.remove(codebase);
118            }
119            return normalizedCodebase;  // We can use the oldValue
120        }
121    
122    
123        static String normalizeCodebase(String input)
124            throws MalformedURLException
125        {
126            assert input != null;
127    
128            StringBuffer codebase = new StringBuffer();
129            StringBuffer working = new StringBuffer();
130            StringTokenizer stok = new StringTokenizer(input, " \t\n\r\f", true);
131            
132            while (stok.hasMoreTokens()) {
133                String item = stok.nextToken();
134                // Optimisation: This optimisation to prevent unnecessary MalformedURLExceptions 
135                //   being generated is most helpful on windows where directory names in the path 
136                //   often contain spaces.  E.G:
137                //     file:/C:/Program Files/Apache Software Foundation/Maven 1.0.2/lib/ant-1.5.3-1.jar
138                //
139                //   Therefore we won't attempt URL("Files/Apache) or URL(" ") for the path delimiter.
140                if ( item.indexOf(':') != -1 )
141                {
142                    try {
143                        URL url = new URL(item);
144                        // If we got this far then item is a valid url, so commit the current
145                        // buffer and start collecting any trailing bits from where we are now
146                        updateCodebase(working, codebase);
147                    } catch (MalformedURLException ignore) {
148                        // just keep going & append to the working buffer
149                    }
150                }
151                
152                // Append the URL or delimiter to the working buffer
153                working.append(item);
154            }
155            
156            // Handle trailing elements
157            updateCodebase(working, codebase);
158            
159            // System.out.println("Normalized codebase: " + codebase);
160            return codebase.toString();
161        }
162        
163        private static void updateCodebase(final StringBuffer working, final StringBuffer codebase)
164            throws MalformedURLException
165        {
166            if (working.length() != 0) {
167                // Normalize the URL
168                URL url = normalizeURL(new URL(working.toString()));
169                // System.out.println("Created normalized URL: " + url);
170                
171                // Put spaces back in for URL delims
172                if (codebase.length() != 0) {
173                    codebase.append(" ");
174                }
175                codebase.append(url);
176                
177                // Reset the working buffer
178                working.setLength(0);
179            }
180        }
181        
182        static URL normalizeURL(URL url)
183        {
184            assert url != null;
185            
186            if (url.getProtocol().equals("file")) {
187                String filename = url.getFile().replace('/', File.separatorChar);
188                File file = new File(filename);
189                try {
190                    url = file.toURI().toURL();
191                }
192                catch (MalformedURLException ignore) {}
193            }
194            
195            return url;
196        }
197        
198        public interface ClassLoaderServerAware {
199            public URL[] getClassLoaderServerURLs();
200        }
201    }