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