001    /*
002     * Copyright 2005 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.depot;
020    
021    import java.io.IOException;
022    import java.rmi.server.RMIClassLoader;
023    import java.rmi.server.RMIClassLoaderSpi;
024    import java.net.MalformedURLException;
025    import java.net.URL;
026    import java.net.URLClassLoader;
027    import java.security.Permission;
028    import java.util.Collections;
029    import java.util.IdentityHashMap;
030    import java.util.Map;
031    
032    import net.dpml.lang.StandardClassLoader;
033    
034    /**
035     * The DepotRMIClassLoaderSpi handles the loading of classes that are based on 
036     * plugin artifact types.
037     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
038     * @version 1.0.3
039     */
040    public class DepotRMIClassLoaderSpi extends RMIClassLoaderSpi
041    {
042        private static final Map LOADERS = Collections.synchronizedMap( new IdentityHashMap( 5 ) );
043    
044        private RMIClassLoaderSpi m_delegate = RMIClassLoader.getDefaultProviderInstance();
045    
046        static
047        {
048            for( ClassLoader classloader = ClassLoader.getSystemClassLoader(); 
049              classloader != null; classloader = classloader.getParent() )
050            {
051                LOADERS.put( classloader, null );
052            }
053        }
054    
055       /**
056        * Default constructor.
057        */
058        public DepotRMIClassLoaderSpi()
059        {
060            super();
061        }
062    
063       /**
064        * Provides the implementation for
065        * {@link RMIClassLoader#loadClass(URL,String)},
066        * {@link RMIClassLoader#loadClass(String,String)}, and
067        * {@link RMIClassLoader#loadClass(String,String,ClassLoader)}.
068        *
069        * Loads a class from a codebase URL path, optionally using the
070        * supplied loader.
071        *
072        * @param codebase the list of URLs (separated by spaces) to load
073        *   the class from, or <code>null</code>
074        * @param name the name of the class to load
075        * @param defaultLoader additional contextual class loader
076        *   to use, or <code>null</code>
077        * @return the <code>Class</code> object representing the loaded class
078        * @exception MalformedURLException if <code>codebase</code> is
079        *   non-<code>null</code> and contains an invalid URL, or
080        *   if <code>codebase</code> is <code>null</code> and the system
081        *   property <code>java.rmi.server.codebase</code> contains an
082        *   invalid URL
083        * @exception ClassNotFoundException if a definition for the class
084        *   could not be found at the specified location
085        */
086        public Class loadClass(
087          String codebase, String name, ClassLoader defaultLoader )
088          throws MalformedURLException, ClassNotFoundException
089        {
090            //if( null != codebase )
091            //{
092            //    final String message = 
093            //      "Loading class: " 
094            //      + name 
095            //      + "\nCodebase: " 
096            //      + codebase;
097            //    getLogger().debug( message );
098            //}
099            return m_delegate.loadClass( codebase, name, defaultLoader );
100        }
101        
102       /**
103        * Provides the implementation for
104        * {@link RMIClassLoader#loadProxyClass(String,String[],ClassLoader)}.
105        *
106        * Loads a dynamic proxy class (see {@link java.lang.reflect.Proxy}
107        * that implements a set of interfaces with the given names
108        * from a codebase URL path, optionally using the supplied loader.
109        * 
110        * <p>An implementation of this method must either return a proxy
111        * class that implements the named interfaces or throw an exception.
112        *
113        * @param codebase the list of URLs (space-separated) to load
114        * classes from, or <code>null</code>
115        * @param interfaces the names of the interfaces for the proxy class
116        * to implement
117        * @param defaultLoader additional contextual class loader
118        * to use, or <code>null</code>
119        * @return a dynamic proxy class that implements the named interfaces
120        * @exception MalformedURLException if <code>codebase</code> is
121        * non-<code>null</code> and contains an invalid URL, or
122        * if <code>codebase</code> is <code>null</code> and the system
123        * property <code>java.rmi.server.codebase</code> contains an
124        * invalid URL
125        * @exception ClassNotFoundException if a definition for one of
126        * the named interfaces could not be found at the specified location,
127        * or if creation of the dynamic proxy class failed (such as if
128        * {@link java.lang.reflect.Proxy#getProxyClass(ClassLoader,Class[])}
129        * would throw an <code>IllegalArgumentException</code> for the given
130        * interface list)
131        */
132        public Class loadProxyClass( 
133          String codebase, String[] interfaces, ClassLoader defaultLoader )
134          throws MalformedURLException, ClassNotFoundException
135        {
136            //if( null != codebase )
137            //{
138            //    getLogger().debug( "Loading proxy: " + codebase );
139            //}
140            return m_delegate.loadProxyClass( codebase, interfaces, defaultLoader );
141        }
142    
143       /**
144        * Provides the implementation for
145        * {@link RMIClassLoader#getClassLoader(String)}.
146        *
147        * Returns a class loader that loads classes from the given codebase
148        * URL path.
149        *
150        * <p>If there is a security manger, its <code>checkPermission</code>
151        * method will be invoked with a
152        * <code>RuntimePermission("getClassLoader")</code> permission;
153        * this could result in a <code>SecurityException</code>.
154        * The implementation of this method may also perform further security
155        * checks to verify that the calling context has permission to connect
156        * to all of the URLs in the codebase URL path.
157        *
158        * @param codebase the list of URLs (space-separated) from which
159        * the returned class loader will load classes from, or <code>null</code>
160        * @return a class loader that loads classes from the given codebase URL
161        * path
162        * @exception MalformedURLException if <code>codebase</code> is
163        * non-<code>null</code> and contains an invalid URL, or
164        * if <code>codebase</code> is <code>null</code> and the system
165        * property <code>java.rmi.server.codebase</code> contains an
166        * invalid URL
167        * @exception SecurityException if there is a security manager and the
168        * invocation of its <code>checkPermission</code> method fails, or
169        * if the caller does not have permission to connect to all of the
170        * URLs in the codebase URL path
171        */
172        public ClassLoader getClassLoader( String codebase )
173        throws MalformedURLException, SecurityException 
174        {
175            return m_delegate.getClassLoader( codebase );
176        }
177    
178       /**
179        * Provides the implementation for
180        * {@link RMIClassLoader#getClassAnnotation(Class)}.
181        *
182        * Returns the annotation string (representing a location for
183        * the class definition) that RMI will use to annotate the class
184        * descriptor when marshalling objects of the given class.
185        *
186        * @param cl the class to obtain the annotation for
187        * @return a string to be used to annotate the given class when
188        * it gets marshalled, or <code>null</code>
189        * @exception NullPointerException if <code>cl</code> is <code>null</code>
190        */
191        public String getClassAnnotation( Class cl ) throws NullPointerException
192        {
193            final String annotations = getAnnotation( cl );
194            //if( null != annotations )
195            //{
196            //    System.out.println( "# " + cl.getName() + ", " + annotations );
197            //}
198            return annotations;
199        }
200    
201        private String getAnnotation( Class cl ) throws NullPointerException
202        {
203            String classname = cl.getName();
204            int i = classname.length();
205            if( ( i > 0 ) && classname.charAt( 0 ) == '[' )
206            {
207                int j;
208                for( j=1; i > j && classname.charAt( j ) == '['; j++ )
209                {
210                    if( ( i > j ) && classname.charAt( j ) != 'L' )
211                    {
212                        return null;
213                    }
214                }
215            }
216    
217            ClassLoader classloader = cl.getClassLoader();
218            if( classloader == null || LOADERS.containsKey( classloader ) )
219            {
220                return System.getProperty( "java.rmi.server.codebase" );
221            }
222    
223            String annotations = null;
224            if( classloader instanceof StandardClassLoader )
225            {
226                annotations = ( (StandardClassLoader) classloader ).getAnnotations();
227            }
228            else if( classloader instanceof URLClassLoader )
229            {
230                annotations = getAnnotations( (URLClassLoader) classloader );
231            }
232    
233            if( annotations != null )
234            {
235                return annotations;
236            }
237            else
238            {
239                return System.getProperty( "java.rmi.server.codebase" );
240            }
241        }
242    
243        private String getAnnotations( URLClassLoader classloader )
244        {
245            StringBuffer buffer = new StringBuffer();
246            return getAnnotations( buffer, classloader );
247        }
248    
249        private String getAnnotations( StringBuffer buffer, URLClassLoader classloader )
250        {
251            packAnnotations( buffer, classloader );
252            String result = buffer.toString();
253            return result.trim();
254        }
255    
256        private void packAnnotations( StringBuffer buffer, URLClassLoader classloader )
257        {
258            if( ClassLoader.getSystemClassLoader() == classloader )
259            {
260                return;
261            }
262    
263            ClassLoader parent = classloader.getParent();
264            if( ( null != parent ) && ( parent instanceof URLClassLoader ) )
265            {
266                packAnnotations( buffer, (URLClassLoader) parent );
267            }
268    
269            try
270            {
271                URL[] urls = classloader.getURLs();
272                if( null != urls )
273                {
274                    SecurityManager manager = System.getSecurityManager();
275                    if( manager != null )
276                    {
277                        for( int k = 0; k < urls.length; k++ )
278                        {
279                            Permission permission = urls[k].openConnection().getPermission();
280                            if( permission != null )
281                            {
282                                manager.checkPermission( permission );
283                            }
284                        }
285                    }
286                    buffer.append( urlsToPath( urls ) + " " );
287                }
288            }
289            catch( SecurityException e ) 
290            {
291                boolean ignore = true; // ignore
292            }
293            catch( IOException e )
294            {
295                boolean ignore = true; // ignore
296            }
297        }
298    
299        private static String urlsToPath( URL[] urls )
300        {
301            if( urls.length == 0 )
302            {
303                return null;
304            }
305            else if( urls.length == 1 )
306            {
307                final String path = urls[0].toExternalForm();
308                if( !path.startsWith( "file:" ) )
309                {
310                    return path;
311                }
312                else
313                {
314                    return "";
315                }
316            }
317            StringBuffer buffer = new StringBuffer( urls[0].toExternalForm() );
318            for( int i=1; i < urls.length; i++ )
319            {
320                final String path = urls[i].toExternalForm();
321                if( !path.startsWith( "file:" ) )
322                {
323                    buffer.append( ' ' );
324                    buffer.append( path );
325                }
326    
327            }
328            return buffer.toString();
329        }
330    }