Added a responder to serve static content from the classloader.
[jsvc.git] / src / dolda / jsvc / j2ee / Archive.java
1 package dolda.jsvc.j2ee;
2
3 import dolda.jsvc.util.*;
4 import java.util.*;
5 import java.io.*;
6 import java.net.*;
7 import java.util.zip.*;
8 import java.util.jar.*;
9
10 public class Archive {
11     private Properties props = defprops();
12     private JarOutputStream zipout = null;
13     private final OutputStream realout;
14
15     public Archive(OutputStream out) {
16         this.realout = out;
17     }
18
19     private void initzip() throws IOException {
20         Manifest man = new Manifest();
21         man.getMainAttributes().put(new Attributes.Name("Manifest-Version"), "1.0");
22         man.getMainAttributes().put(new Attributes.Name("Created-By"), "jsvc");
23         JarOutputStream zip = new JarOutputStream(realout, man);
24         zip.putNextEntry(new ZipEntry("WEB-INF/"));
25         zip.putNextEntry(new ZipEntry("WEB-INF/lib/"));
26         this.zipout = zip;
27     }
28
29     private ZipOutputStream zip() throws IOException {
30         if(zipout == null)
31             initzip();
32         return(this.zipout);
33     }
34     
35     public void putprop(String key, String val) {
36         props.put(key, val);
37     }
38
39     public void loadprops(InputStream in) throws IOException {
40         props.load(in);
41     }
42
43     public void jarprops(File[] jars, String propres) throws IOException {
44         URL[] urls = new URL[jars.length];
45         try {
46             for(int i = 0; i < jars.length; i++)
47                 urls[i] = new URL("file", "", jars[i].toString());
48         } catch(MalformedURLException e) {
49             throw(new Error(e));
50         }
51         ClassLoader cl = new URLClassLoader(urls);
52         InputStream in = cl.getResourceAsStream(propres);
53         if(in != null) {
54             try {
55                 props.load(in);
56             } finally {
57                 in.close();
58             }
59         }
60     }
61
62     private static Properties defprops() {
63         Properties props = new Properties();
64         props.put("jsvc.j2ee.webxml.coding", "UTF-8");
65         return(props);
66     }
67
68     private static class MissingPropException extends RuntimeException {
69         public final String prop;
70         
71         private MissingPropException(String prop) {
72             super("Missing required property " + prop);
73             this.prop = prop;
74         }
75     }
76
77     private static String subst(String ln, Properties props) {
78         int p = 0;
79         while((p = ln.indexOf("${", p)) >= 0) {
80             int p2 = ln.indexOf('}', p + 2);
81             String pn = ln.substring(p + 2, p2);
82             String pv = (String)props.get(pn);
83             if(pv == null)
84                 throw(new MissingPropException(pn));
85             ln = ln.substring(0, p) + pv + ln.substring(p2 + 1);
86             p = p + pv.length();
87         }
88         return(ln);
89     }
90
91     private void writewebxml() throws IOException {
92         zip().putNextEntry(new ZipEntry("WEB-INF/web.xml"));
93         InputStream tmpl = Archive.class.getResourceAsStream("web.xml.template");
94         String cs = (String)props.get("jsvc.j2ee.webxml.coding");
95         try {
96             BufferedReader r = new BufferedReader(new InputStreamReader(tmpl, "US-ASCII"));
97             BufferedWriter w = new BufferedWriter(new OutputStreamWriter(zip(), cs));
98             String ln;
99             while((ln = r.readLine()) != null) {
100                 w.write(subst(ln, props));
101                 w.write('\n');
102             }
103             w.flush();
104         } finally {
105             tmpl.close();
106         }
107     }
108     
109     public void addcode(String name, InputStream in) throws IOException {
110         zip().putNextEntry(new ZipEntry("WEB-INF/classes/" + name));
111         Misc.cpstream(in, zip());
112     }
113
114     public void addjars(File[] jars) throws IOException {
115         jarprops(jars, "jsvc.properties");
116         ZipOutputStream zip = zip();
117         for(File jar : jars) {
118             zip.putNextEntry(new ZipEntry("WEB-INF/lib/" + jar.getName()));
119             InputStream jarin = new FileInputStream(jar);
120             try {
121                 Misc.cpstream(jarin, zip);
122             } finally {
123                 jarin.close();
124             }
125         }
126     }
127
128     public void finish() throws IOException {
129         zip().finish();
130     }
131     
132     public static class AntTask extends org.apache.tools.ant.Task {
133         private org.apache.tools.ant.types.FileSet jars, code;
134         private File props, outfile;
135         private String appname;
136         
137         private static File[] getfiles(org.apache.tools.ant.types.FileSet fs) {
138             org.apache.tools.ant.DirectoryScanner ds = fs.getDirectoryScanner();
139             ds.scan();
140             String[] nms = ds.getIncludedFiles();
141             File[] ret = new File[nms.length];
142             for(int i = 0; i < nms.length; i++)
143                 ret[i] = new File(ds.getBasedir(), nms[i]);
144             return(ret);
145         }
146
147         private void rebuild(File[] jars, File[] code) throws IOException {
148             OutputStream out = new FileOutputStream(outfile);
149             
150             System.out.println("Building " + outfile);
151             
152             try {
153                 Archive ar = new Archive(out);
154                 if(appname != null)
155                     ar.putprop("jsvc.j2ee.appname", appname);
156                 if(props != null) {
157                     InputStream in = new FileInputStream(props);
158                     try {
159                         ar.loadprops(in);
160                     } finally {
161                         in.close();
162                     }
163                 }
164                 
165                 for(File f : code) {
166                     InputStream in = new FileInputStream(f);
167                     try {
168                         ar.addcode(f.getName(), in);
169                     } finally {
170                         in.close();
171                     }
172                 }
173                 
174                 ar.addjars(jars);
175                 ar.writewebxml();
176                 
177                 ar.finish();
178             } catch(MissingPropException e) {
179                 throw(new org.apache.tools.ant.BuildException(e.getMessage(), e));
180             } finally {
181                 out.close();
182             }
183         }
184         
185         public void execute() {
186             File[] jars = (this.jars != null)?getfiles(this.jars):new File[0];
187             File[] code = (this.code != null)?getfiles(this.code):new File[0];
188             if(jars.length < 1)
189                 throw(new org.apache.tools.ant.BuildException("Must have at least one JAR file", getLocation()));
190             if(outfile == null)
191                 throw(new org.apache.tools.ant.BuildException("No output file specified", getLocation()));
192             
193             Collection<File> deps = new LinkedList<File>();
194             deps.addAll(Arrays.asList(jars));
195             deps.addAll(Arrays.asList(code));
196             if(props != null)
197                 deps.add(props);
198             
199             boolean rebuild = false;
200             for(File dep : deps) {
201                 if(!dep.exists())
202                     throw(new org.apache.tools.ant.BuildException(dep + " does not exist", getLocation()));
203                 if(dep.lastModified() > outfile.lastModified()) {
204                     rebuild = true;
205                     break;
206                 }
207             }
208             
209             if(rebuild) {
210                 try {
211                     rebuild(jars, code);
212                 } catch(IOException e) {
213                     throw(new org.apache.tools.ant.BuildException(e));
214                 }
215             }
216         }
217
218         public void addJars(org.apache.tools.ant.types.FileSet path) {
219             this.jars = path;
220         }
221         
222         public void setJars(org.apache.tools.ant.types.FileSet path) {
223             this.jars = path;
224         }
225         
226         public void setCode(org.apache.tools.ant.types.FileSet path) {
227             this.code = path;
228         }
229         
230         public void setPropfile(File propfile) {
231             this.props = propfile;
232         }
233         
234         public void setDestfile(File outfile) {
235             this.outfile = outfile;
236         }
237         
238         public void setAppname(String name) {
239             this.appname = name;
240         }
241     }
242
243     private static void usage(PrintStream out) {
244         out.println("usage: dolda.jsvc.j2ee.Archive [-h] [-p PROPFILE] [-n DISPLAY-NAME] [(-c CODE-FILE)...] WAR-FILE JAR-FILE...");
245     }
246     
247     public static void main(String[] args) throws IOException {
248         PosixArgs opt = PosixArgs.getopt(args, "hp:n:c:");
249         if(opt == null) {
250             usage(System.err);
251             System.exit(1);
252         }
253         if(opt.rest.length < 2) {
254             usage(System.err);
255             System.exit(1);
256         }
257         String war = opt.rest[0];
258         File[] jars = new File[opt.rest.length - 1];
259         for(int i = 1; i < opt.rest.length; i++)
260             jars[i - 1] = new File(opt.rest[i]);
261         
262         OutputStream out = new FileOutputStream(war);
263         try {
264             Archive ar = new Archive(out);
265         
266             for(char c : opt.parsed()) {
267                 switch(c) {
268                 case 'p':
269                     {
270                         InputStream in = new FileInputStream(opt.arg);
271                         try {
272                             ar.loadprops(in);
273                         } finally {
274                             in.close();
275                         }
276                     }
277                     break;
278                 case 'n':
279                     ar.putprop("jsvc.j2ee.appname", opt.arg);
280                     break;
281                 case 'c':
282                     {
283                         File f = new File(opt.arg);
284                         InputStream in = new FileInputStream(f);
285                         try {
286                             ar.addcode(f.getName(), in);
287                         } finally {
288                             in.close();
289                         }
290                     }
291                     break;
292                 case 'h':
293                     usage(System.out);
294                     return;
295                 }
296             }
297             
298             ar.addjars(jars);
299             ar.writewebxml();
300             
301             ar.finish();
302         } finally {
303             out.close();
304         }
305     }
306 }