source: SRUServer/trunk/src/main/java/eu/clarin/sru/server/SRUXMLStreamWriter.java @ 2729

Last change on this file since 2729 was 2729, checked in by oschonef, 11 years ago
  • add Index Data repository in favor of CLARIN repository (for cql-java depencency)
  • update cql-java dependency to version 1.11
  • Property svn:eol-style set to native
File size: 15.6 KB
Line 
1/**
2 * This software is copyright (c) 2011-2013 by
3 *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
4 * This is free software. You can redistribute it
5 * and/or modify it under the terms described in
6 * the GNU General Public License v3 of which you
7 * should have received a copy. Otherwise you can download
8 * it from
9 *
10 *   http://www.gnu.org/licenses/gpl-3.0.txt
11 *
12 * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
13 *
14 * @license http://www.gnu.org/licenses/gpl-3.0.txt
15 *  GNU General Public License v3
16 */
17package eu.clarin.sru.server;
18
19import java.io.IOException;
20import java.io.OutputStream;
21import java.io.OutputStreamWriter;
22import java.io.StringReader;
23import java.io.Writer;
24import java.util.ArrayDeque;
25import java.util.Deque;
26
27import javax.xml.namespace.NamespaceContext;
28import javax.xml.parsers.SAXParser;
29import javax.xml.parsers.SAXParserFactory;
30import javax.xml.stream.XMLOutputFactory;
31import javax.xml.stream.XMLStreamException;
32import javax.xml.stream.XMLStreamWriter;
33
34import org.xml.sax.Attributes;
35import org.xml.sax.InputSource;
36import org.xml.sax.SAXException;
37import org.xml.sax.helpers.DefaultHandler;
38import org.z3950.zing.cql.CQLNode;
39
40final class SRUXMLStreamWriter implements XMLStreamWriter {
41    private enum IndentingState {
42        SEEN_NOTHING,
43        SEEN_ELEMENT,
44        SEEN_DATA
45    }
46    private static final SAXParserFactory factory;
47    private final SRURecordPacking packing;
48    private final Writer writer;
49    private final XMLStreamWriter xmlwriter;
50    private int indent = -1;
51    private int depth = 0;
52    private Deque<IndentingState> stateStack;
53    private IndentingState state;
54    private boolean writingRecord = false;
55
56
57    SRUXMLStreamWriter(OutputStream stream, XMLOutputFactory factory,
58            SRURecordPacking recordPacking, int indent) throws IOException,
59            XMLStreamException {
60        this.packing = recordPacking;
61        this.writer = new OutputStreamWriter(stream,
62                SRUServer.RESPONSE_ENCODING) {
63            @Override
64            public void write(int c) throws IOException {
65                if (writingRecord && (packing == SRURecordPacking.STRING)) {
66                    /*
67                     * NOTE: need to write single characters here, because
68                     * super.write(String) will call us again, and we would
69                     * create and endless loop here until stack blows up ...
70                     */
71                    switch (c) {
72                    case '<':
73                        super.write('&');
74                        super.write('l');
75                        super.write('t');
76                        super.write(';');
77                        return;
78                    case '>':
79                        super.write('&');
80                        super.write('g');
81                        super.write('t');
82                        super.write(';');
83                        return;
84                    case '&':
85                        super.write('&');
86                        super.write('a');
87                        super.write('m');
88                        super.write('p');
89                        super.write(';');
90                        return;
91                    default:
92                        /* $FALL-THROUGH$ */
93                    }
94                }
95                super.write(c);
96            }
97
98            @Override
99            public void write(char[] c, int off, int len) throws IOException {
100                if (writingRecord && (packing == SRURecordPacking.STRING)) {
101                    for (int i = off; i < len; i++) {
102                        this.write(c[i]);
103                    }
104                } else {
105                    super.write(c, off, len);
106                }
107            }
108
109            @Override
110            public void write(String s, int off, int len) throws IOException {
111                if (writingRecord && (packing == SRURecordPacking.STRING)) {
112                    for (int i = off; i < len; i++) {
113                        this.write(s.charAt(i));
114                    }
115                } else {
116                    super.write(s, off, len);
117                }
118            }
119        };
120
121        /*
122         * It is believed, that XMLWriterFactories, once configured,
123         * are thread-save ...
124         */
125        this.xmlwriter = factory.createXMLStreamWriter(this.writer);
126
127        if (indent > 0) {
128            this.indent = indent;
129            this.state = IndentingState.SEEN_NOTHING;
130            this.stateStack = new ArrayDeque<IndentingState>(16);
131        }
132    }
133
134
135    @Override
136    public void writeStartElement(String localName) throws XMLStreamException {
137        if (indent > 0) {
138            onStartElement();
139        }
140        xmlwriter.writeStartElement(localName);
141    }
142
143
144    @Override
145    public void writeStartElement(String namespaceURI, String localName)
146            throws XMLStreamException {
147        if (indent > 0) {
148            onStartElement();
149        }
150        xmlwriter.writeStartElement(namespaceURI, localName);
151    }
152
153
154    @Override
155    public void writeStartElement(String prefix, String localName,
156            String namespaceURI) throws XMLStreamException {
157        if (indent > 0) {
158            onStartElement();
159        }
160        xmlwriter.writeStartElement(prefix, localName, namespaceURI);
161    }
162
163
164    @Override
165    public void writeEmptyElement(String namespaceURI, String localName)
166            throws XMLStreamException {
167        if (indent > 0) {
168            onEmptyElement();
169        }
170        xmlwriter.writeEmptyElement(namespaceURI, localName);
171    }
172
173
174    @Override
175    public void writeEmptyElement(String prefix, String localName,
176            String namespaceURI) throws XMLStreamException {
177        if (indent > 0) {
178            onEmptyElement();
179        }
180        xmlwriter.writeEmptyElement(prefix, localName, namespaceURI);
181    }
182
183
184    @Override
185    public void writeEmptyElement(String localName) throws XMLStreamException {
186        if (indent > 0) {
187            onEmptyElement();
188        }
189        xmlwriter.writeEmptyElement(localName);
190    }
191
192
193    @Override
194    public void writeEndElement() throws XMLStreamException {
195        if (indent > 0) {
196            onEndElement();
197        }
198        xmlwriter.writeEndElement();
199    }
200
201
202    @Override
203    public void writeEndDocument() throws XMLStreamException {
204        xmlwriter.writeEndDocument();
205    }
206
207
208    @Override
209    public void close() throws XMLStreamException {
210        xmlwriter.close();
211    }
212
213
214    @Override
215    public void flush() throws XMLStreamException {
216        xmlwriter.flush();
217    }
218
219
220    @Override
221    public void writeAttribute(String localName, String value)
222            throws XMLStreamException {
223        xmlwriter.writeAttribute(localName, value);
224    }
225
226
227    @Override
228    public void writeAttribute(String prefix, String namespaceURI,
229            String localName, String value) throws XMLStreamException {
230        xmlwriter.writeAttribute(prefix, namespaceURI, localName, value);
231    }
232
233
234    @Override
235    public void writeAttribute(String namespaceURI, String localName,
236            String value) throws XMLStreamException {
237        xmlwriter.writeAttribute(namespaceURI, localName, value);
238    }
239
240
241    @Override
242    public void writeNamespace(String prefix, String namespaceURI)
243            throws XMLStreamException {
244        xmlwriter.writeNamespace(prefix, namespaceURI);
245    }
246
247
248    @Override
249    public void writeDefaultNamespace(String namespaceURI)
250            throws XMLStreamException {
251        xmlwriter.writeDefaultNamespace(namespaceURI);
252    }
253
254
255    @Override
256    public void writeComment(String data) throws XMLStreamException {
257        xmlwriter.writeComment(data);
258    }
259
260
261    @Override
262    public void writeProcessingInstruction(String target)
263            throws XMLStreamException {
264        xmlwriter.writeProcessingInstruction(target);
265    }
266
267
268    @Override
269    public void writeProcessingInstruction(String target, String data)
270            throws XMLStreamException {
271        xmlwriter.writeProcessingInstruction(target, data);
272    }
273
274
275    @Override
276    public void writeCData(String data) throws XMLStreamException {
277        if (indent > 0) {
278            state = IndentingState.SEEN_DATA;
279        }
280        xmlwriter.writeCData(data);
281    }
282
283
284    @Override
285    public void writeDTD(String dtd) throws XMLStreamException {
286        xmlwriter.writeDTD(dtd);
287    }
288
289
290    @Override
291    public void writeEntityRef(String name) throws XMLStreamException {
292        xmlwriter.writeEntityRef(name);
293    }
294
295
296    @Override
297    public void writeStartDocument() throws XMLStreamException {
298        xmlwriter.writeStartDocument();
299        if (indent > 0) {
300            xmlwriter.writeCharacters("\n");
301        }
302    }
303
304
305    @Override
306    public void writeStartDocument(String version) throws XMLStreamException {
307        xmlwriter.writeStartDocument(version);
308        if (indent > 0) {
309            xmlwriter.writeCharacters("\n");
310        }
311    }
312
313
314    @Override
315    public void writeStartDocument(String encoding, String version)
316            throws XMLStreamException {
317        xmlwriter.writeStartDocument(encoding, version);
318        if (indent > 0) {
319            xmlwriter.writeCharacters("\n");
320        }
321    }
322
323
324    @Override
325    public void writeCharacters(String text) throws XMLStreamException {
326        if (indent > 0) {
327            state = IndentingState.SEEN_DATA;
328        }
329        xmlwriter.writeCharacters(text);
330    }
331
332
333    @Override
334    public void writeCharacters(char[] text, int start, int len)
335            throws XMLStreamException {
336        if (indent > 0) {
337            state = IndentingState.SEEN_DATA;
338        }
339        xmlwriter.writeCharacters(text, start, len);
340    }
341
342
343    @Override
344    public String getPrefix(String uri) throws XMLStreamException {
345        return xmlwriter.getPrefix(uri);
346    }
347
348
349    @Override
350    public void setPrefix(String prefix, String uri) throws XMLStreamException {
351        xmlwriter.setPrefix(prefix, uri);
352    }
353
354
355    @Override
356    public void setDefaultNamespace(String uri) throws XMLStreamException {
357        xmlwriter.setDefaultNamespace(uri);
358    }
359
360
361    @Override
362    public void setNamespaceContext(NamespaceContext context)
363            throws XMLStreamException {
364        xmlwriter.setNamespaceContext(context);
365    }
366
367
368    @Override
369    public NamespaceContext getNamespaceContext() {
370        return xmlwriter.getNamespaceContext();
371    }
372
373
374    @Override
375    public Object getProperty(String name) throws IllegalArgumentException {
376        return xmlwriter.getProperty(name);
377    }
378
379
380    public Writer getWriter() {
381        return writer;
382    }
383
384
385    public void startRecord() throws XMLStreamException {
386        if (writingRecord) {
387            throw new IllegalStateException("was already writing record");
388        }
389        xmlwriter.flush();
390        /*
391         *  abuse writeCharacters to force writer to close finish
392         *  any pending start or end elements
393         */
394        xmlwriter.writeCharacters("");
395        writingRecord = true;
396    }
397
398
399    public void endRecord() throws XMLStreamException {
400        if (!writingRecord) {
401            throw new IllegalStateException("was not writing record");
402        }
403        /*
404         *  abuse writeCharacters to force writer to close finish
405         *  any pending start or end elements
406         */
407        xmlwriter.writeCharacters("");
408        xmlwriter.flush();
409        writingRecord = false;
410    }
411
412
413    public void writeXCQL(CQLNode query, final boolean searchRetrieveMode)
414            throws XMLStreamException {
415        /*
416         * HACK: Parsing the XCQL to serialize is wasting resources.
417         * Alternative would be to serialize to XCQL from CQLNode, but
418         * I'm not yet enthusiastic on writing the serializer myself.
419         */
420        try {
421            SAXParser parser = factory.newSAXParser();
422            InputSource input =
423                    new InputSource(new StringReader(query.toXCQL()));
424            parser.parse(input, new DefaultHandler() {
425                @Override
426                public void startElement(String uri, String localName,
427                        String qName, Attributes attributes)
428                                throws SAXException {
429                    if (!searchRetrieveMode && qName.equals("searchClause")) {
430                        return;
431                    }
432                    try {
433                        SRUXMLStreamWriter.this.writeStartElement(qName);
434                        for (int i = 0; i < attributes.getLength(); i++) {
435                            SRUXMLStreamWriter.this.writeAttribute(attributes.getQName(i),
436                                    attributes.getValue(i));
437                        }
438                    } catch (XMLStreamException e) {
439                        throw new SAXException(e);
440                    }
441                }
442
443                @Override
444                public void endElement(String uri, String localName,
445                        String qName) throws SAXException {
446                    if (!searchRetrieveMode && qName.equals("searchClause")) {
447                        return;
448                    }
449                    try {
450                        SRUXMLStreamWriter.this.writeEndElement();
451                    } catch (XMLStreamException e) {
452                        throw new SAXException(e);
453                    }
454                }
455
456                @Override
457                public void characters(char[] text, int start, int length)
458                        throws SAXException {
459                    try {
460                        boolean isWhitespaceOnly = true;
461                        for (int i = start; i < start + length; i++) {
462                            if (!Character.isWhitespace(text[i])) {
463                                isWhitespaceOnly = false;
464                                break;
465                            }
466                        }
467                        if (!isWhitespaceOnly) {
468                            SRUXMLStreamWriter.this.writeCharacters(text, start, length);
469                        }
470                    } catch (XMLStreamException e) {
471                        throw new SAXException(e);
472                    }
473                }
474            });
475        } catch (Exception e) {
476            throw new XMLStreamException("cannot write XCQL", e);
477        }
478    }
479
480
481    private void onStartElement() throws XMLStreamException {
482        if (!(writingRecord && (packing == SRURecordPacking.STRING))) {
483            stateStack.push(IndentingState.SEEN_ELEMENT);
484            state = IndentingState.SEEN_NOTHING;
485            if (depth > 0) {
486                xmlwriter.writeCharacters("\n");
487            }
488            doIndent();
489            depth++;
490        }
491    }
492
493
494    private void onEndElement() throws XMLStreamException {
495        if (!(writingRecord && (packing == SRURecordPacking.STRING))) {
496            depth--;
497            if (state == IndentingState.SEEN_ELEMENT) {
498                xmlwriter.writeCharacters("\n");
499                doIndent();
500            }
501            state = stateStack.pop();
502        }
503    }
504
505
506    private void onEmptyElement() throws XMLStreamException {
507        if (!(writingRecord && (packing == SRURecordPacking.STRING))) {
508            state = IndentingState.SEEN_ELEMENT;
509            if (depth > 0) {
510                xmlwriter.writeCharacters("\n");
511            }
512            doIndent();
513        }
514    }
515
516
517    private void doIndent() throws XMLStreamException {
518        if (depth > 0) {
519            for (int i = 0; i < (depth * indent); i++) {
520                xmlwriter.writeCharacters(" ");
521            }
522        }
523    }
524
525
526    static {
527        factory = SAXParserFactory.newInstance();
528        factory.setNamespaceAware(false);
529        factory.setValidating(false);
530        factory.setXIncludeAware(false);
531    }
532
533} // class SRUXMLStreamWriter
Note: See TracBrowser for help on using the repository browser.