1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 package com.sun.star.lib.loader;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.UnsupportedEncodingException;
34 import java.lang.reflect.InvocationTargetException;
35 import java.lang.reflect.Method;
36 import java.net.JarURLConnection;
37 import java.net.MalformedURLException;
38 import java.net.URL;
39 import java.net.URLClassLoader;
40 import java.util.Enumeration;
41 import java.util.jar.Attributes;
42 import java.util.jar.Manifest;
43 import java.util.StringTokenizer;
44 import java.util.Vector;
45 
46 /**
47  * This class can be used as a loader for application classes which use UNO.
48  *
49  * <p>The Loader class detects a UNO installation on the system and adds the
50  * UNO jar files to the search path of a customized class loader, which is used
51  * for loading the application classes.</p>
52  */
53 public final class Loader {
54 
55     private static ClassLoader m_Loader = null;
56 
57     /**
58      * do not instantiate
59      */
60     private Loader() {}
61 
62     /**
63      * The main method instantiates a customized class loader with the
64      * UNO jar files added to the search path and loads the application class,
65      * which is specified in the Main-Class attribute of the
66      * com/sun/star/lib/Loader.class entry of the manifest file or
67      * as first parameter in the argument list.
68      */
69     public static void main( String[] arguments ) throws Exception {
70 
71         // get the name of the class to be loaded from the manifest
72         String className = null;
73         Class clazz = Loader.class;
74         ClassLoader loader = clazz.getClassLoader();
75         Vector res = new Vector();
76         try {
77             Enumeration en = loader.getResources( "META-INF/MANIFEST.MF" );
78             while ( en.hasMoreElements() ) {
79                 res.add( (URL) en.nextElement() );
80             }
81             // the jarfile with the com/sun/star/lib/loader/Loader.class
82             // per-entry attribute is most probably the last resource in the
83             // list, therefore search backwards
84             for ( int i = res.size() - 1; i >= 0; i-- ) {
85                 URL jarurl = (URL) res.elementAt( i );
86                 try {
87                     JarURLConnection jarConnection =
88                         (JarURLConnection) jarurl.openConnection();
89                     Manifest mf = jarConnection.getManifest();
90                     Attributes attrs = (Attributes) mf.getAttributes(
91                         "com/sun/star/lib/loader/Loader.class" );
92                     if ( attrs != null ) {
93                         className = attrs.getValue( "Application-Class" );
94                         if ( className != null )
95                             break;
96                     }
97                 } catch ( IOException e ) {
98                     // if an I/O error occurs when opening a new
99                     // JarURLConnection, ignore this manifest file
100                     System.err.println( "com.sun.star.lib.loader.Loader::" +
101                                         "main: bad manifest file: " + e );
102                 }
103             }
104         } catch ( IOException e ) {
105             // if an I/O error occurs when getting the manifest resources,
106             // try to get the name of the class to be loaded from the argument
107             // list
108             System.err.println( "com.sun.star.lib.loader.Loader::" +
109                                 "main: cannot get manifest resources: " + e );
110         }
111 
112         // if no manifest entry was found, get the name of the class
113         // to be loaded from the argument list
114         String[] args;
115         if ( className == null ) {
116             if ( arguments.length > 0 ) {
117                 className = arguments[0];
118                 args = new String[arguments.length - 1];
119                 System.arraycopy( arguments, 1, args, 0, args.length );
120             } else {
121                 throw new IllegalArgumentException(
122                     "The name of the class to be loaded must be either " +
123                     "specified in the Main-Class attribute of the " +
124                     "com/sun/star/lib/loader/Loader.class entry " +
125                     "of the manifest file or as a command line argument." );
126             }
127         } else {
128             args = arguments;
129         }
130 
131         // load the class with the customized class loader and
132         // invoke the main method
133         if ( className != null ) {
134             ClassLoader cl = getCustomLoader();
135             Thread.currentThread().setContextClassLoader(cl);
136             Class c = cl.loadClass( className );
137             Method m = c.getMethod( "main", new Class[] { String[].class } );
138             m.invoke( null, new Object[] { args } );
139         }
140     }
141 
142     /**
143      * Gets the customized class loader with the UNO jar files added to the
144      * search path.
145      *
146      * @return the customized class loader
147      */
148     public static synchronized ClassLoader getCustomLoader() {
149 
150         final String CLASSESDIR = "classes";
151         final String JUHJAR = "juh.jar";
152 
153         if ( m_Loader == null ) {
154 
155             // get the urls from which to load classes and resources
156             // from the class path
157             Vector vec = new Vector();
158             String classpath = null;
159             try {
160                 classpath = System.getProperty( "java.class.path" );
161             } catch ( SecurityException e ) {
162                 // don't add the class path entries to the list of class
163                 // loader URLs
164                 System.err.println( "com.sun.star.lib.loader.Loader::" +
165                     "getCustomLoader: cannot get system property " +
166                     "java.class.path: " + e );
167             }
168             if ( classpath != null ) {
169                 addUrls(vec, classpath, File.pathSeparator);
170             }
171 
172             // get the urls from which to load classes and resources
173             // from the UNO installation
174             String path = InstallationFinder.getPath();
175             if ( path != null ) {
176                 File fClassesDir = new File( path, CLASSESDIR );
177                 File fJuh = new File( fClassesDir, JUHJAR );
178                 if ( fJuh.exists() ) {
179                     URL[] clurls = new URL[1];
180                     try {
181                         clurls[0] = fJuh.toURL();
182                         ClassLoader cl = new CustomURLClassLoader( clurls );
183                         Class c = cl.loadClass(
184                             "com.sun.star.comp.helper.UnoInfo" );
185                         Method m = c.getMethod( "getJars", (Class[]) null );
186                         URL[] jarurls = (URL[]) m.invoke(
187                             null, (Object[]) null );
188                         for ( int i = 0; i < jarurls.length; i++ ) {
189                             vec.add( jarurls[i] );
190                         }
191                     } catch ( MalformedURLException e ) {
192                         // don't add the UNO jar files to the list of class
193                         // loader URLs
194                         System.err.println( "com.sun.star.lib.loader.Loader::" +
195                             "getCustomLoader: cannot add UNO jar files: " + e );
196                     } catch ( ClassNotFoundException e ) {
197                         // don't add the UNO jar files to the list of class
198                         // loader URLs
199                         System.err.println( "com.sun.star.lib.loader.Loader::" +
200                             "getCustomLoader: cannot add UNO jar files: " + e );
201                     } catch ( NoSuchMethodException e ) {
202                         // don't add the UNO jar files to the list of class
203                         // loader URLs
204                         System.err.println( "com.sun.star.lib.loader.Loader::" +
205                             "getCustomLoader: cannot add UNO jar files: " + e );
206                     } catch ( IllegalAccessException e ) {
207                         // don't add the UNO jar files to the list of class
208                         // loader URLs
209                         System.err.println( "com.sun.star.lib.loader.Loader::" +
210                             "getCustomLoader: cannot add UNO jar files: " + e );
211                     } catch ( InvocationTargetException e ) {
212                         // don't add the UNO jar files to the list of class
213                         // loader URLs
214                         System.err.println( "com.sun.star.lib.loader.Loader::" +
215                             "getCustomLoader: cannot add UNO jar files: " + e );
216                     }
217                 } else {
218                     callUnoinfo(path, vec);
219                 }
220             } else {
221                 System.err.println( "com.sun.star.lib.loader.Loader::" +
222                     "getCustomLoader: no UNO installation found!" );
223             }
224 
225             // copy urls to array
226             URL[] urls = new URL[vec.size()];
227             vec.toArray( urls );
228 
229             // instantiate class loader
230             m_Loader = new CustomURLClassLoader( urls );
231         }
232 
233         return m_Loader;
234     }
235 
236     private static void addUrls(Vector urls, String data, String delimiter) {
237         StringTokenizer tokens = new StringTokenizer( data, delimiter );
238         while ( tokens.hasMoreTokens() ) {
239             try {
240                 urls.add( new File( tokens.nextToken() ).toURL() );
241             } catch ( MalformedURLException e ) {
242                 // don't add this class path entry to the list of class loader
243                 // URLs
244                 System.err.println( "com.sun.star.lib.loader.Loader::" +
245                     "getCustomLoader: bad pathname: " + e );
246             }
247         }
248     }
249 
250     private static void callUnoinfo(String path, Vector urls) {
251         Process p;
252         try {
253             p = Runtime.getRuntime().exec(
254                 new String[] { new File(path, "unoinfo").getPath(), "java" });
255         } catch (IOException e) {
256             System.err.println(
257                 "com.sun.star.lib.loader.Loader::getCustomLoader: exec" +
258                 " unoinfo: " + e);
259             return;
260         }
261         new Drain(p.getErrorStream()).start();
262         int code;
263         byte[] buf = new byte[1000];
264         int n = 0;
265         try {
266             InputStream s = p.getInputStream();
267             code = s.read();
268             for (;;) {
269                 if (n == buf.length) {
270                     if (n > Integer.MAX_VALUE / 2) {
271                         System.err.println(
272                             "com.sun.star.lib.loader.Loader::getCustomLoader:" +
273                             " too much unoinfo output");
274                         return;
275                     }
276                     byte[] buf2 = new byte[2 * n];
277                     for (int i = 0; i < n; ++i) {
278                         buf2[i] = buf[i];
279                     }
280                     buf = buf2;
281                 }
282                 int k = s.read(buf, n, buf.length - n);
283                 if (k == -1) {
284                     break;
285                 }
286                 n += k;
287             }
288         } catch (IOException e) {
289             System.err.println(
290                 "com.sun.star.lib.loader.Loader::getCustomLoader: reading" +
291                 " unoinfo output: " + e);
292             return;
293         }
294         int ev;
295         try {
296             ev = p.waitFor();
297         } catch (InterruptedException e) {
298             Thread.currentThread().interrupt();
299             System.err.println(
300                 "com.sun.star.lib.loader.Loader::getCustomLoader: waiting for" +
301                 " unoinfo: " + e);
302             return;
303         }
304         if (ev != 0) {
305             System.err.println(
306                 "com.sun.star.lib.loader.Loader::getCustomLoader: unoinfo"
307                 + " exit value " + n);
308             return;
309         }
310         String s;
311         if (code == '0') {
312             s = new String(buf);
313         } else if (code == '1') {
314             try {
315                 s = new String(buf, "UTF-16LE");
316             } catch (UnsupportedEncodingException e) {
317                 System.err.println(
318                     "com.sun.star.lib.loader.Loader::getCustomLoader:" +
319                     " transforming unoinfo output: " + e);
320                 return;
321             }
322         } else {
323             System.err.println(
324                 "com.sun.star.lib.loader.Loader::getCustomLoader: bad unoinfo"
325                 + " output");
326             return;
327         }
328         addUrls(urls, s, "\0");
329     }
330 
331     private static final class Drain extends Thread {
332         public Drain(InputStream stream) {
333             super("unoinfo stderr drain");
334             this.stream = stream;
335         }
336 
337         public void run() {
338             try {
339                 while (stream.read() != -1) {}
340             } catch (IOException e) { /* ignored */ }
341         }
342 
343         private final InputStream stream;
344     }
345 
346     /**
347      * A customized class loader which is used to load classes and resources
348      * from a search path of user-defined URLs.
349      */
350     private static final class CustomURLClassLoader extends URLClassLoader {
351 
352         public CustomURLClassLoader( URL[] urls ) {
353             super( urls );
354         }
355 
356         protected Class findClass( String name ) throws ClassNotFoundException {
357             // This is only called via this.loadClass -> super.loadClass ->
358             // this.findClass, after this.loadClass has already called
359             // super.findClass, so no need to call super.findClass again:
360             throw new ClassNotFoundException( name );
361         }
362 
363         protected Class loadClass( String name, boolean resolve )
364             throws ClassNotFoundException
365         {
366             Class c = findLoadedClass( name );
367             if ( c == null ) {
368                 try {
369                     c = super.findClass( name );
370                 } catch ( ClassNotFoundException e ) {
371                     return super.loadClass( name, resolve );
372                 } catch ( SecurityException e ) {
373                     // A SecurityException "Prohibited package name: java.lang"
374                     // may occur when the user added the JVM's rt.jar to the
375                     // java.class.path:
376                     return super.loadClass( name, resolve );
377                 }
378             }
379             if ( resolve ) {
380                 resolveClass( c );
381             }
382             return c;
383         }
384     }
385 }
386