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.system.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 edu.emory.mathcs.backport.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: 487175 $ $Date: 2006-12-14 03:10:31 -0800 (Thu, 14 Dec 2006) $
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, 0.75F);
045    
046    
047        public Class loadClass(String codebase, String name, ClassLoader defaultLoader)
048            throws MalformedURLException, ClassNotFoundException
049        {
050            if (codebase != null) {
051                codebase = getNormalizedCodebase(codebase);
052            }
053            
054            return delegate.loadClass(codebase, name, defaultLoader);
055        }
056        
057        public Class loadProxyClass(String codebase, String[] interfaces, ClassLoader defaultLoader)
058            throws MalformedURLException, ClassNotFoundException
059        {
060            if (codebase != null) {
061                codebase = getNormalizedCodebase(codebase);
062            }
063            
064            return delegate.loadProxyClass(codebase, interfaces, defaultLoader);
065        }
066        
067        public ClassLoader getClassLoader(String codebase)
068            throws MalformedURLException
069        {
070            if (codebase != null) {
071                codebase = getNormalizedCodebase(codebase);
072            }
073            
074            return delegate.getClassLoader(codebase);
075        }
076        
077        public String getClassAnnotation(Class type) {
078            Object obj = type.getClassLoader();
079            if (obj instanceof ClassLoaderServerAware) {
080                ClassLoaderServerAware classLoader = (ClassLoaderServerAware) obj;
081                URL urls[] = classLoader.getClassLoaderServerURLs();
082                if (null == urls) {
083                    return delegate.getClassAnnotation(type);
084                }
085                StringBuffer codebase = new StringBuffer();
086                for (int i = 0; i < urls.length; i++) {
087                    URL url = normalizeURL(urls[i]);
088                    if (codebase.length() != 0) {
089                        codebase.append(' ');
090                    }
091                    codebase.append(url);
092                }
093                return codebase.toString();
094            }
095            
096            return delegate.getClassAnnotation(type);
097        }
098    
099        /**
100         * Uses a ConcurrentReaderHashmap to save the contents of previous parses.
101         *
102         * @param codebase
103         * @return
104         * @throws MalformedURLException
105         */
106        private String getNormalizedCodebase(String codebase)
107                throws MalformedURLException {
108            String cachedCodebase = (String)cachedCodebases.get(codebase);
109            if (cachedCodebase != null)
110                return cachedCodebase;
111    
112            String normalizedCodebase = normalizeCodebase(codebase);
113            String oldValue = (String)cachedCodebases.put(codebase, normalizedCodebase);
114    
115            // If there was a previous value remove the one we just added to make sure the
116            // cache doesn't grow.
117            if (oldValue != null) {
118                cachedCodebases.remove(codebase);
119            }
120            return normalizedCodebase;  // We can use the oldValue
121        }
122    
123    
124        static String normalizeCodebase(String input)
125            throws MalformedURLException
126        {
127            assert input != null;
128    
129            StringBuffer codebase = new StringBuffer();
130            StringBuffer working = new StringBuffer();
131            StringTokenizer stok = new StringTokenizer(input, " \t\n\r\f", true);
132            
133            while (stok.hasMoreTokens()) {
134                String item = stok.nextToken();
135                // Optimisation: This optimisation to prevent unnecessary MalformedURLExceptions 
136                //   being generated is most helpful on windows where directory names in the path 
137                //   often contain spaces.  E.G:
138                //     file:/C:/Program Files/Apache Software Foundation/Maven 1.0.2/lib/ant-1.5.3-1.jar
139                //
140                //   Therefore we won't attempt URL("Files/Apache) or URL(" ") for the path delimiter.
141                if ( item.indexOf(':') != -1 )
142                {
143                    try {
144                        URL url = new URL(item);
145                        // If we got this far then item is a valid url, so commit the current
146                        // buffer and start collecting any trailing bits from where we are now
147                        updateCodebase(working, codebase);
148                    } catch (MalformedURLException ignore) {
149                        // just keep going & append to the working buffer
150                    }
151                }
152                
153                // Append the URL or delimiter to the working buffer
154                working.append(item);
155            }
156            
157            // Handle trailing elements
158            updateCodebase(working, codebase);
159            
160            // System.out.println("Normalized codebase: " + codebase);
161            return codebase.toString();
162        }
163        
164        private static void updateCodebase(final StringBuffer working, final StringBuffer codebase)
165            throws MalformedURLException
166        {
167            if (working.length() != 0) {
168                // Normalize the URL
169                URL url = normalizeURL(new URL(working.toString()));
170                // System.out.println("Created normalized URL: " + url);
171                
172                // Put spaces back in for URL delims
173                if (codebase.length() != 0) {
174                    codebase.append(" ");
175                }
176                codebase.append(url);
177                
178                // Reset the working buffer
179                working.setLength(0);
180            }
181        }
182        
183        static URL normalizeURL(URL url)
184        {
185            assert url != null;
186            
187            if (url.getProtocol().equals("file")) {
188                String filename = url.getFile().replace('/', File.separatorChar);
189                File file = new File(filename);
190                try {
191                    url = file.toURI().toURL();
192                }
193                catch (MalformedURLException ignore) {}
194            }
195            
196            return url;
197        }
198        
199        public interface ClassLoaderServerAware {
200            public URL[] getClassLoaderServerURLs();
201        }
202    }