View Javadoc

1   /*
2    * Copyright (c) 2002 Peter Antman, Teknik i Media  <peter.antman@tim.se>
3    *
4    * $Id: Main.java,v 1.1.1.1 2004/05/19 12:07:30 pra Exp $
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2 of the License, or (at your option) any later version
10   * 
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   */
20  package org.backsource.utils.lang;
21  
22  
23  import java.net.URL;
24  import java.net.URLClassLoader;
25  import java.net.JarURLConnection;
26  import java.net.MalformedURLException;
27  import java.lang.reflect.Method;
28  import java.lang.reflect.Modifier;
29  import java.lang.reflect.InvocationTargetException;
30  import java.util.StringTokenizer;
31  import java.util.jar.Attributes;
32  import java.io.IOException;
33  
34  import java.net.URL;
35  import java.net.URLConnection;
36  import java.net.URLStreamHandler;
37  
38  import java.io.BufferedInputStream;
39  import java.io.BufferedOutputStream;
40  import java.io.DataInputStream;
41  import java.io.File;
42  import java.io.FileOutputStream;
43  import java.io.IOException;
44  import java.io.InputStream;
45  import java.io.OutputStream;
46  
47  import java.util.HashMap;
48  import java.util.Map;
49  /***
50   * A dynamic main to use instead of the normal main i jar-files to be able to load classes from jar-files inside the jarfile run by java -jar.
51   *
52   * <p>main makes it possible to package applications in a jar-file and run the with the java -jar XXX.jar command. This class works like a standin for the main you want to run. by specifying this class as the Main-class in the manifest it is possible to distribute lib:s the applications depends on as embedded jarfiles.</p>
53   *
54   *<p>Main will  add all jar-files mentioned in the Class-path attribute to the classpath without them having to exist outside the running jar-application. It will the start the main application specifyed in the Dynamic-main attribute. A normal manifest could look like this:</p>
55  <pre>
56  Main-class: org.backsource.utils.lang.Main
57  Class-path: lib/xerces2.0.2.jar lib/xalan2.4.D1.jar lib/xml-apis.jar lib/timutils.jar
58  
59  Name: se/tim/utils/lang/Main.class
60  Dynamic-Main: org.backsource.utils.javadoc.XMLEscaper
61  </pre>
62  
63  <p>The libraries would be embedded in the distributed jar-file, which could look like this:</p>
64  
65  <pre>
66  [pra@pra jars]$ unzip -l xmlescaper.jar
67  Archive:  xmlescaper.jar
68    Length     Date   Time    Name
69   --------    ----   ----    ----
70          0  09-09-02 11:58   META-INF/
71        264  09-09-02 11:58   META-INF/MANIFEST.MF
72          0  08-19-02 13:28   se/
73          0  05-21-02 12:41   se/tim/
74          0  08-22-02 12:58   se/tim/utils/
75          0  08-23-02 16:53   se/tim/utils/lang/
76       4544  09-09-02 11:57   se/tim/utils/lang/Main.class
77        987  09-09-02 11:57   se/tim/utils/lang/Main$MainClassLoader.class
78       3174  09-09-02 11:57   se/tim/utils/lang/Main$Handler.class
79          0  09-09-02 11:58   lib/
80     108484  08-22-02 13:16   lib/xml-apis.jar
81     933730  08-22-02 12:59   lib/xerces2.0.2.jar
82     983377  08-22-02 12:59   lib/xalan2.4.D1.jar
83      58971  09-03-02 10:00   lib/timutils.jar
84   --------                   -------
85    2093531                   14 files
86  </pre>
87  
88  } // end of main ()
89  
90  } // end of main ()
91  
92   *
93   *
94   * @author <a href="mailto:pra@tim.se">Peter Antman</a>
95   * @version $Revision: 1.1.1.1 $
96   */
97  
98  public class Main  
99  {
100    public static final Attributes.Name DYNAMIC_MAIN = new Attributes.Name("Dynamic-Main");
101 
102    // We do not want log4j here, this should be a clean class. Turn on debugging only when developing-
103    public static final boolean debug = false;
104    String dynamicMain;
105    URL[] classpath;
106    URL where;
107 
108 
109    public Main () throws Exception{
110       Class c = getClass();
111       where = c.getResource("/" + c.getName().replace('.','/') + ".class");
112 
113       JarURLConnection uc = (JarURLConnection)where.openConnection();
114       Attributes attr = uc.getMainAttributes();
115       if ( attr != null) {
116          
117          setClasspath(uc.getJarFileURL().toString(), attr.getValue(Attributes.Name.CLASS_PATH));
118       }
119       Attributes uattr = uc.getAttributes();
120       dynamicMain = uattr != null ? uattr.getValue(DYNAMIC_MAIN) : null;
121       if (dynamicMain== null ) {
122          throw new Exception("Could not find Dynamic-main to load");
123       } // end of if ()
124    }
125    
126 
127    public void setClasspath(String jarfile, String manifestcl)throws MalformedURLException,IOException {
128       StringTokenizer toker = new StringTokenizer(manifestcl);
129       classpath = new URL[ toker.countTokens() ];
130       for (int i = 0;toker.hasMoreTokens();i++) {
131          URL inside = new URL("njar","",20,"njar:"+jarfile + "^/" + toker.nextToken()+ "^/", new Handler());
132          JarURLConnection uc = (JarURLConnection)inside.openConnection();
133 
134          classpath[i] =new URL("jar:"+uc.getJarFileURL()+"!/");
135 
136          if ( debug) 
137             System.out.println("URL in cl" + classpath[i]);
138       } // end of while ()     
139       
140    }
141 
142    public void invokeClass(String[] args)
143       throws ClassNotFoundException,
144              NoSuchMethodException,
145              InvocationTargetException,
146              IOException
147    {
148       ClassLoader old = Thread.currentThread().getContextClassLoader();
149       URLClassLoader dynLoader = new MainClassLoader(classpath,old);
150       Thread.currentThread().setContextClassLoader(dynLoader);
151 
152       // Debug
153       URL[] deb = dynLoader.getURLs();
154       for ( int i = 0;i<deb.length;i++) {
155          if ( debug)
156             System.out.println(deb[i]);
157 
158          //JarURLConnection uc = (JarURLConnection)deb[i].openConnection();
159          // System.out.println("Jarurlcon:" +  uc.getJarFileURL() );
160       } // end of for ()
161       
162 
163       Class c = dynLoader.loadClass(dynamicMain);
164       Method m = c.getMethod("main", new Class[] { args.getClass() });
165       m.setAccessible(true);
166       int mods = m.getModifiers();
167       if (m.getReturnType() != void.class || !Modifier.isStatic(mods) ||
168           !Modifier.isPublic(mods)) {
169          throw new NoSuchMethodException("main");
170       }
171       try {
172          m.invoke(null, new Object[] { args });
173       } catch (IllegalAccessException e) {
174          // This should not happen, as we have disabled access checks
175       }finally {
176          Thread.currentThread().setContextClassLoader(old);         
177       } // end of finally
178       
179    }
180    
181    public void pwd() throws Exception {
182       Class c = getClass();
183       URL where = c.getResource("/" + c.getName().replace('.','/') + ".class");
184       if ( debug) 
185       System.out.println(where);
186       JarURLConnection uc = (JarURLConnection)where.openConnection();
187       Attributes attr = uc.getMainAttributes();
188       if ( debug) {
189          System.out.println( uc.getJarFileURL() );
190          System.out.println( attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null);
191          System.out.println( attr != null ? attr.getValue(Attributes.Name.CLASS_PATH ) : null);
192                
193       } // end of if ()
194       Attributes uattr = uc.getAttributes();
195       if ( debug) {
196 
197       
198          System.out.println( uattr != null ? uattr.getValue(DYNAMIC_MAIN) : null);
199 
200          System.out.println(where.getFile());
201          System.out.println(where.getHost());
202          System.out.println(where.getPath());
203          
204       } // end of if ()
205    }
206    
207    public static void main(String[] args) throws Exception {
208       Main  m = new Main();
209       //m.pwd();
210       m.invokeClass(args);
211       
212    }
213 
214    public class MainClassLoader extends URLClassLoader {
215       MainClassLoader(URL[] path, ClassLoader parent) {
216          super(path,parent);
217       }
218 
219       protected Class findClass(String name)
220          throws ClassNotFoundException{
221             if (debug) 
222               System.out.println("Looking up resource " + name);
223             return super.findClass(name);
224          }
225    }
226    
227    public class Handler
228       extends URLStreamHandler
229    {
230       // URL protocol designations
231       public static final String PROTOCOL = "njar";
232       public static final String NJAR_SEPARATOR = "^/";
233       public static final String JAR_SEPARATOR = "!/";
234       
235       protected Map savedJars = new HashMap();
236 
237       public URLConnection openConnection(final URL url)
238          throws IOException
239       {
240          String file = url.getFile();
241          String embeddedURL = file;
242          String jarPath = "";
243       
244          int pos = file.lastIndexOf(NJAR_SEPARATOR);
245          if (pos >= 0)
246             {
247                embeddedURL = file.substring(0, pos);
248                if (file.length() > pos + NJAR_SEPARATOR.length())
249                   jarPath = file.substring(pos + NJAR_SEPARATOR.length());
250             }
251 
252          if (embeddedURL.startsWith(PROTOCOL))
253             {
254                if (debug)
255                   System.out.println("Opening next  nested jar: " + embeddedURL);
256                File tempJar = (File) savedJars.get(embeddedURL);
257                if (tempJar == null)
258                   {
259                      if (debug)
260                         System.out.println(embeddedURL);
261                      InputStream embededData = new URL(url,embeddedURL, new Handler()).openStream();
262                      tempJar = File.createTempFile("nested-", ".jar");
263                      tempJar.deleteOnExit();
264                      //System.out.println("temp file location : " + tempJar);
265                      storeJar(embededData, new FileOutputStream(tempJar));
266                      savedJars.put(embeddedURL, tempJar);
267                   }
268 
269                String t = tempJar.getCanonicalFile().toURL().toExternalForm();
270                if ( debug) 
271                   System.out.println("file URL : " + t);
272                t = "njar:" + t + NJAR_SEPARATOR + jarPath;
273                if ( debug)
274                   System.out.println("Opening saved jar: " + t);
275          
276                return new URL(url,t,new Handler()).openConnection();
277          
278             }
279          else
280             {
281                if ( debug)
282                   System.out.println("Opening final nested jar: " + "jar:" +embeddedURL+ JAR_SEPARATOR + jarPath);
283                return new URL("jar:" + embeddedURL + JAR_SEPARATOR + jarPath).openConnection();
284             }
285       }
286 
287       protected void storeJar(final InputStream in, final OutputStream out) 
288          throws IOException
289       {
290 
291          BufferedInputStream bis = null;
292          BufferedOutputStream bos = null;
293          try
294          {
295             bis = new BufferedInputStream(in);
296             bos = new BufferedOutputStream(out);
297 
298             byte data[] = new byte[512];
299             int c;
300             while ((c = bis.read(data)) >= 0)
301                {
302                   bos.write(data, 0, c);
303                }
304 
305          }
306          finally
307          {
308             try
309             {
310                bis.close();
311             }
312             catch (IOException ignore)
313             {
314             }
315             try
316             {
317                bos.close();
318             }
319             catch (IOException ignore)
320             {
321             }
322          }
323       }
324 
325    } // Main
326 }