KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > serialization > ZipArchiveSerializer


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.serialization;
17
18 import java.io.FilterOutputStream JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.util.Enumeration JavaDoc;
22 import java.util.zip.ZipEntry JavaDoc;
23 import java.util.zip.ZipOutputStream JavaDoc;
24
25 import org.apache.avalon.framework.activity.Disposable;
26 import org.apache.avalon.framework.service.ServiceException;
27 import org.apache.avalon.framework.service.ServiceManager;
28 import org.apache.avalon.framework.service.ServiceSelector;
29 import org.apache.avalon.framework.service.Serviceable;
30 import org.apache.excalibur.source.Source;
31 import org.apache.excalibur.source.SourceResolver;
32 import org.xml.sax.Attributes JavaDoc;
33 import org.xml.sax.SAXException JavaDoc;
34 import org.xml.sax.helpers.NamespaceSupport JavaDoc;
35
36 /**
37  * A serializer that builds Zip archives by aggregating several sources.
38  * <p>
39  * The input document should describe entries of the archive by means of
40  * their name (which can be a path) and their content either as URLs or
41  * inline data :
42  * <ul>
43  * <li>URLs, given by the "src" attribute, are Cocoon sources and as such
44  * can use any of the protocols handled by Cocoon, including "cocoon:" to
45  * include dynamically generated content in the archive.</li>
46  * <li>inline data is represented by an XML document that is serialized to the
47  * zip entry using the serializer identified by the "serializer" attribute.</li>
48  * </ul>
49  * <p>
50  * Example :
51  * <pre>
52  * &lt;zip:archive xmlns:zip="http://apache.org/cocoon/zip-archive/1.0"&gt;
53  * &lt;zip:entry name="foo.html" SRC="cocoon://dynFoo.html"/&gt;
54  * &lt;zip:entry name="images/bar.jpeg" SRC="bar.jpeg"/&gt;
55  * &lt;zip:entry name="index.html" serializer="html"&gt;
56  * &lt;html&gt;
57  * &lt;head&gt;
58  * &lt;title&gt;Index page&lt;/title&gt;
59  * &lt;/head&gt;
60  * &lt;body&gt;
61  * Please go &lt;a HREF="foo.html"&gt;there&lt;/a&gt;
62  * &lt;/body&lt;
63  * &lt;/html&gt;
64  * &lt;/zip:entry&gt;
65  * &lt;/zip:archive:zip&gt;
66  * </pre>
67  *
68  * @author <a HREF="http://www.apache.org/~sylvain">Sylvain Wallez</a>
69  * @version $Id: ZipArchiveSerializer.java 331284 2005-11-07 15:47:34Z sylvain $
70  */

71
72 // TODO (1) : handle more attributes on <archive> for properties of ZipOutputStream
73
// such as comment or default compression method and level
74

75 // TODO (2) : handle more attributes on <entry> for properties of ZipEntry
76
// (compression method and level, time, comment, etc.)
77

78 public class ZipArchiveSerializer extends AbstractSerializer
79                                   implements Disposable, Serviceable {
80
81     /**
82      * The namespace for elements handled by this serializer,
83      * "http://apache.org/cocoon/zip-archive/1.0".
84      */

85     public static final String JavaDoc ZIP_NAMESPACE = "http://apache.org/cocoon/zip-archive/1.0";
86
87     private static final int START_STATE = 0;
88     private static final int IN_ZIP_STATE = 1;
89     private static final int IN_CONTENT_STATE = 2;
90
91     /** The component manager */
92     protected ServiceManager manager;
93
94     /** The serializer component selector */
95     protected ServiceSelector selector;
96
97     /** The Zip stream where entries will be written */
98     protected ZipOutputStream JavaDoc zipOutput;
99
100     /** The current state */
101     protected int state = START_STATE;
102
103     /** The resolver to get sources */
104     protected SourceResolver resolver;
105
106     /** Temporary byte buffer to read source data */
107     protected byte[] buffer;
108
109     /** Serializer used when in IN_CONTENT state */
110     protected Serializer serializer;
111
112     /** Current depth of the serialized content */
113     protected int contentDepth;
114
115     /** Used to collect namespaces */
116     private NamespaceSupport JavaDoc nsSupport = new NamespaceSupport JavaDoc();
117
118     /**
119      * Store exception
120      */

121     private SAXException JavaDoc exception;
122
123
124     /**
125      * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
126      */

127     public void service(ServiceManager manager) throws ServiceException {
128         this.manager = manager;
129         this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
130     }
131
132     /**
133      * Returns default mime type for zip archives, <code>application/zip</code>.
134      * Can be overridden in the sitemap.
135      * @return application/zip
136      */

137     public String JavaDoc getMimeType() {
138         return "application/zip";
139     }
140
141     /**
142      * @see org.xml.sax.ContentHandler#startDocument()
143      */

144     public void startDocument() throws SAXException JavaDoc {
145         this.state = START_STATE;
146         this.zipOutput = new ZipOutputStream JavaDoc(this.output);
147     }
148
149     /**
150      * Begin the scope of a prefix-URI Namespace mapping.
151      *
152      * @param prefix The Namespace prefix being declared.
153      * @param uri The Namespace URI the prefix is mapped to.
154      */

155     public void startPrefixMapping(String JavaDoc prefix, String JavaDoc uri) throws SAXException JavaDoc {
156         if (state == IN_CONTENT_STATE && this.contentDepth > 0) {
157             // Pass to the serializer
158
super.startPrefixMapping(prefix, uri);
159
160         } else {
161             // Register it if it's not our own namespace (useless to content)
162
if (!uri.equals(ZIP_NAMESPACE)) {
163                 this.nsSupport.declarePrefix(prefix, uri);
164             }
165         }
166     }
167     
168     public void endPrefixMapping(String JavaDoc prefix) throws SAXException JavaDoc {
169         if (state == IN_CONTENT_STATE && this.contentDepth > 0) {
170             // Pass to the serializer
171
super.endPrefixMapping(prefix);
172         }
173     }
174
175     // Note : no need to implement endPrefixMapping() as we just need to pass it through if there
176
// is a serializer, which is what the superclass does.
177

178     /**
179      * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
180      */

181     public void startElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc atts)
182         throws SAXException JavaDoc {
183
184         // Damage control. Sometimes one exception is just not enough...
185
if (this.exception != null) {
186             throw this.exception;
187         }
188
189         switch (state) {
190             case START_STATE:
191                 // expecting "zip" as the first element
192
if (namespaceURI.equals(ZIP_NAMESPACE) && localName.equals("archive")) {
193                     this.nsSupport.pushContext();
194                     this.state = IN_ZIP_STATE;
195                 } else {
196                     throw this.exception =
197                         new SAXException JavaDoc("Expecting 'archive' root element (got '" + localName + "')");
198                 }
199                 break;
200
201             case IN_ZIP_STATE:
202                 // expecting "entry" element
203
if (namespaceURI.equals(ZIP_NAMESPACE) && localName.equals("entry")) {
204                     this.nsSupport.pushContext();
205                     // Get the source
206
addEntry(atts);
207                 } else {
208                     throw this.exception =
209                         new SAXException JavaDoc("Expecting 'entry' element (got '" + localName + "')");
210                 }
211                 break;
212
213             case IN_CONTENT_STATE:
214                 if (this.contentDepth == 0) {
215                     // Give it any namespaces already declared
216
Enumeration JavaDoc prefixes = this.nsSupport.getPrefixes();
217                     while (prefixes.hasMoreElements()) {
218                         String JavaDoc prefix = (String JavaDoc) prefixes.nextElement();
219                         super.startPrefixMapping(prefix, this.nsSupport.getURI(prefix));
220                     }
221                 }
222
223                 this.contentDepth++;
224                 super.startElement(namespaceURI, localName, qName, atts);
225                 break;
226         }
227     }
228
229     /**
230      * @see org.xml.sax.ContentHandler#characters(char[], int, int)
231      */

232     public void characters(char[] buffer, int offset, int length) throws SAXException JavaDoc {
233         // Propagate text to the serializer only if we have encountered the content's top-level
234
// element. Otherwhise, the serializer may be confused by some characters occuring between
235
// startDocument() and the first startElement() (e.g. Batik fails hard in that case)
236
if (this.state == IN_CONTENT_STATE && this.contentDepth > 0) {
237             super.characters(buffer, offset, length);
238         }
239     }
240
241     /**
242      * Add an entry in the archive.
243      * @param atts the attributes that describe the entry
244      */

245     protected void addEntry(Attributes JavaDoc atts) throws SAXException JavaDoc {
246         String JavaDoc name = atts.getValue("name");
247         if (name == null) {
248             throw this.exception =
249                 new SAXException JavaDoc("No name given to the Zip entry");
250         }
251
252         String JavaDoc src = atts.getValue("src");
253         String JavaDoc serializerType = atts.getValue("serializer");
254
255         if (src == null && serializerType == null) {
256             throw this.exception =
257                 new SAXException JavaDoc("No source nor serializer given for the Zip entry '" + name + "'");
258         }
259
260         if (src != null && serializerType != null) {
261             throw this.exception =
262                 new SAXException JavaDoc("Cannot specify both 'src' and 'serializer' on a Zip entry '" + name + "'");
263         }
264
265         Source source = null;
266         try {
267             // Create a new Zip entry
268
ZipEntry JavaDoc entry = new ZipEntry JavaDoc(name);
269             this.zipOutput.putNextEntry(entry);
270
271             if (src != null) {
272                 // Get the source and its data
273
source = resolver.resolveURI(src);
274                 InputStream JavaDoc sourceInput = source.getInputStream();
275                 
276                 // Buffer lazily allocated
277
if (this.buffer == null)
278                     this.buffer = new byte[1024];
279
280                 // Copy the source to the zip
281
int len;
282                 while ((len = sourceInput.read(this.buffer)) > 0) {
283                     this.zipOutput.write(this.buffer, 0, len);
284                 }
285
286                 // and close the entry
287
this.zipOutput.closeEntry();
288
289             } else {
290                 // Serialize content
291
if (this.selector == null) {
292                     this.selector =
293                         (ServiceSelector) this.manager.lookup(Serializer.ROLE + "Selector");
294                 }
295
296                 // Get the serializer
297
this.serializer = (Serializer) this.selector.select(serializerType);
298
299                 // Direct its output to the zip file, filtering calls to close()
300
// (we don't want the archive to be closed by the serializer)
301
this.serializer.setOutputStream(new FilterOutputStream JavaDoc(this.zipOutput) {
302                     public void close() { /* nothing */ }
303                 });
304
305                 // Set it as the current XMLConsumer
306
setConsumer(serializer);
307
308                 // start its document
309
this.serializer.startDocument();
310
311                 this.state = IN_CONTENT_STATE;
312                 this.contentDepth = 0;
313             }
314
315         } catch (RuntimeException JavaDoc re) {
316             throw re;
317         } catch (SAXException JavaDoc se) {
318             throw this.exception = se;
319         } catch (Exception JavaDoc e) {
320             throw this.exception = new SAXException JavaDoc(e);
321         } finally {
322             this.resolver.release( source );
323         }
324     }
325
326     /**
327      * @see org.xml.sax.ContentHandler#endElement(String, String, String)
328      */

329     public void endElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName)
330         throws SAXException JavaDoc {
331
332         // Damage control. Sometimes one exception is just not enough...
333
if (this.exception != null) {
334             throw this.exception;
335         }
336
337         if (state == IN_CONTENT_STATE) {
338             super.endElement(namespaceURI, localName, qName);
339             this.contentDepth--;
340
341             if (this.contentDepth == 0) {
342                 // End of this entry
343

344                 // close all declared namespaces.
345
Enumeration JavaDoc prefixes = this.nsSupport.getPrefixes();
346                 while (prefixes.hasMoreElements()) {
347                     String JavaDoc prefix = (String JavaDoc) prefixes.nextElement();
348                     super.endPrefixMapping(prefix);
349                 }
350
351                 super.endDocument();
352
353                 try {
354                     this.zipOutput.closeEntry();
355                 } catch (IOException JavaDoc ioe) {
356                     throw this.exception = new SAXException JavaDoc(ioe);
357                 }
358
359                 super.setConsumer(null);
360                 this.selector.release(this.serializer);
361                 this.serializer = null;
362
363                 // Go back to listening for entries
364
this.state = IN_ZIP_STATE;
365             }
366         } else {
367             this.nsSupport.popContext();
368         }
369     }
370
371     /**
372      * @see org.xml.sax.ContentHandler#endDocument()
373      */

374     public void endDocument() throws SAXException JavaDoc {
375         try {
376             // Close the zip archive
377
this.zipOutput.finish();
378
379         } catch (IOException JavaDoc ioe) {
380             throw new SAXException JavaDoc(ioe);
381         }
382     }
383
384     /**
385      * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
386      */

387     public void recycle() {
388         this.exception = null;
389         if (this.serializer != null) {
390             this.selector.release(this.serializer);
391         }
392         if (this.selector != null) {
393             this.manager.release(this.selector);
394         }
395
396         this.nsSupport.reset();
397         super.recycle();
398     }
399
400     /* (non-Javadoc)
401      * @see org.apache.avalon.framework.activity.Disposable#dispose()
402      */

403     public void dispose() {
404         if (this.manager != null) {
405             this.manager.release(this.resolver);
406             this.resolver = null;
407             this.manager = null;
408         }
409     }
410 }
411
Popular Tags