001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    
019    package org.apache.geronimo.system.rmi;
020    
021    import java.net.MalformedURLException;
022    import java.net.URL;
023    
024    import java.io.File;
025    
026    import java.util.StringTokenizer;
027    
028    import java.rmi.server.RMIClassLoader;
029    import java.rmi.server.RMIClassLoaderSpi;
030    
031    import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
032    
033    /**
034     * An implementation of {@link RMIClassLoaderSpi} which provides normilzation
035     * of codebase URLs and delegates to the default {@link RMIClassLoaderSpi}.
036     *
037     * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
038     */
039    public class RMIClassLoaderSpiImpl
040        extends RMIClassLoaderSpi
041    {
042        private RMIClassLoaderSpi delegate = RMIClassLoader.getDefaultProviderInstance();
043    
044        //TODO: Not sure of the best initial size.  Starting with 100 which should be reasonable.
045        private ConcurrentHashMap cachedCodebases = new ConcurrentHashMap(100, 0.75F);
046    
047    
048        public Class loadClass(String codebase, String name, ClassLoader defaultLoader)
049            throws MalformedURLException, ClassNotFoundException
050        {
051            if (codebase != null) {
052                codebase = getNormalizedCodebase(codebase);
053            }
054            
055            return delegate.loadClass(codebase, name, defaultLoader);
056        }
057        
058        public Class loadProxyClass(String codebase, String[] interfaces, ClassLoader defaultLoader)
059            throws MalformedURLException, ClassNotFoundException
060        {
061            if (codebase != null) {
062                codebase = getNormalizedCodebase(codebase);
063            }
064            
065            return delegate.loadProxyClass(codebase, interfaces, defaultLoader);
066        }
067        
068        public ClassLoader getClassLoader(String codebase)
069            throws MalformedURLException
070        {
071            if (codebase != null) {
072                codebase = getNormalizedCodebase(codebase);
073            }
074            
075            return delegate.getClassLoader(codebase);
076        }
077        
078        public String getClassAnnotation(Class type) {
079            Object obj = type.getClassLoader();
080            if (obj instanceof ClassLoaderServerAware) {
081                ClassLoaderServerAware classLoader = (ClassLoaderServerAware) obj;
082                URL urls[] = classLoader.getClassLoaderServerURLs();
083                if (null == urls) {
084                    return delegate.getClassAnnotation(type);
085                }
086                StringBuffer codebase = new StringBuffer();
087                for (int i = 0; i < urls.length; i++) {
088                    URL url = normalizeURL(urls[i]);
089                    if (codebase.length() != 0) {
090                        codebase.append(' ');
091                    }
092                    codebase.append(url);
093                }
094                return codebase.toString();
095            }
096            
097            return delegate.getClassAnnotation(type);
098        }
099    
100        /**
101         * Uses a ConcurrentReaderHashmap to save the contents of previous parses.
102         *
103         * @param codebase
104         * @return
105         * @throws MalformedURLException
106         */
107        private String getNormalizedCodebase(String codebase)
108                throws MalformedURLException {
109            String cachedCodebase = (String)cachedCodebases.get(codebase);
110            if (cachedCodebase != null)
111                return cachedCodebase;
112    
113            String normalizedCodebase = normalizeCodebase(codebase);
114            String oldValue = (String)cachedCodebases.put(codebase, normalizedCodebase);
115    
116            // If there was a previous value remove the one we just added to make sure the
117            // cache doesn't grow.
118            if (oldValue != null) {
119                cachedCodebases.remove(codebase);
120            }
121            return normalizedCodebase;  // We can use the oldValue
122        }
123    
124    
125        static String normalizeCodebase(String input)
126            throws MalformedURLException
127        {
128            assert input != null;
129    
130            StringBuffer codebase = new StringBuffer();
131            StringBuffer working = new StringBuffer();
132            StringTokenizer stok = new StringTokenizer(input, " \t\n\r\f", true);
133            
134            while (stok.hasMoreTokens()) {
135                String item = stok.nextToken();
136                // Optimisation: This optimisation to prevent unnecessary MalformedURLExceptions 
137                //   being generated is most helpful on windows where directory names in the path 
138                //   often contain spaces.  E.G:
139                //     file:/C:/Program Files/Apache Software Foundation/Maven 1.0.2/lib/ant-1.5.3-1.jar
140                //
141                //   Therefore we won't attempt URL("Files/Apache) or URL(" ") for the path delimiter.
142                if ( item.indexOf(':') != -1 )
143                {
144                    try {
145                        URL url = new URL(item);
146                        // If we got this far then item is a valid url, so commit the current
147                        // buffer and start collecting any trailing bits from where we are now
148                        updateCodebase(working, codebase);
149                    } catch (MalformedURLException ignore) {
150                        // just keep going & append to the working buffer
151                    }
152                }
153                
154                // Append the URL or delimiter to the working buffer
155                working.append(item);
156            }
157            
158            // Handle trailing elements
159            updateCodebase(working, codebase);
160            
161            // System.out.println("Normalized codebase: " + codebase);
162            return codebase.toString();
163        }
164        
165        private static void updateCodebase(final StringBuffer working, final StringBuffer codebase)
166            throws MalformedURLException
167        {
168            if (working.length() != 0) {
169                // Normalize the URL
170                URL url = normalizeURL(new URL(working.toString()));
171                // System.out.println("Created normalized URL: " + url);
172                
173                // Put spaces back in for URL delims
174                if (codebase.length() != 0) {
175                    codebase.append(" ");
176                }
177                codebase.append(url);
178                
179                // Reset the working buffer
180                working.setLength(0);
181            }
182        }
183        
184        static URL normalizeURL(URL url)
185        {
186            assert url != null;
187            
188            if (url.getProtocol().equals("file")) {
189                String filename = url.getFile().replace('/', File.separatorChar);
190                File file = new File(filename);
191                try {
192                    url = file.toURI().toURL();
193                }
194                catch (MalformedURLException ignore) {}
195            }
196            
197            return url;
198        }
199        
200        public interface ClassLoaderServerAware {
201            public URL[] getClassLoaderServerURLs();
202        }
203    }