source: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/Response.java @ 5367

Last change on this file since 5367 was 5367, checked in by Oliver Schonefeld, 10 years ago
  • Property svn:eol-style set to native
File size: 22.2 KB
Line 
1package eu.clarin.cmdi.oai.harvester.impl;
2
3import java.io.FilterInputStream;
4import java.io.IOException;
5import java.io.InputStream;
6import java.util.ArrayList;
7import java.util.List;
8import java.util.zip.GZIPInputStream;
9import java.util.zip.Inflater;
10import java.util.zip.InflaterInputStream;
11
12import javax.xml.namespace.NamespaceContext;
13import javax.xml.namespace.QName;
14import javax.xml.stream.Location;
15import javax.xml.stream.XMLStreamConstants;
16import javax.xml.stream.XMLStreamException;
17import javax.xml.stream.XMLStreamReader;
18import javax.xml.stream.XMLStreamWriter;
19
20import org.apache.http.HttpEntity;
21import org.apache.http.HttpResponse;
22import org.apache.http.client.utils.HttpClientUtils;
23import org.apache.http.util.EntityUtils;
24import org.codehaus.stax2.XMLStreamReader2;
25import org.slf4j.Logger;
26import org.slf4j.LoggerFactory;
27
28import eu.clarin.cmdi.oai.harvester.HarvesterException;
29import eu.clarin.cmdi.oai.harvester.HarvesterProtocolErrorException;
30import eu.clarin.cmdi.oai.harvester.ProtocolError;
31
32
33final class Response {
34    private static final class CountingInputStream extends FilterInputStream {
35        private long count = 0;
36
37
38        private CountingInputStream(final InputStream stream) {
39            super(stream);
40        }
41
42
43        @Override
44        public int read() throws IOException {
45            final int result = super.read();
46            count += (result > 0) ? 1 : 0;
47            return result;
48        }
49
50
51        @Override
52        public int read(byte[] buffer, int offset, int length)
53                throws IOException {
54            final int result = super.read(buffer, offset, length);
55            count += (result > 0) ? result : 0;
56            return result;
57        }
58
59
60        @Override
61        public int read(byte[] buffer) throws IOException {
62            final int result = super.read(buffer);
63            count += (result > 0) ? result : 0;
64            return result;
65        }
66
67
68        @Override
69        public long skip(long n) throws IOException {
70            final long result = super.skip(n);
71            count += (result > 0) ? n : 0;
72            return result;
73        }
74
75
76        public long getByteCount() {
77            return count;
78        }
79
80    } // class CountingInputStream
81    private static final String OAI_NS = "http://www.openarchives.org/OAI/2.0/";
82    private static final String ENCODING_GZIP = "gzip";
83    private static final String ENCODING_DEFLATE = "defalte";
84    private static final Logger logger =
85            LoggerFactory.getLogger(Response.class);
86    private final HttpResponse response;
87    private final long now = System.currentTimeMillis();
88    private final CountingInputStream stream;
89    private final XMLStreamReader2 reader;
90
91
92    Response(HarvestJobImpl job, HttpResponse response, AbstractHarvester harvester)
93            throws IOException, XMLStreamException, HarvesterException {
94        if (job == null) {
95           throw new NullPointerException("job == null");
96        }
97        if (response == null) {
98            throw new NullPointerException("response == null");
99        }
100        if (harvester == null) {
101            throw new NullPointerException("harvester == null");
102        }
103        this.response = response;
104
105        final HttpEntity entity = response.getEntity();
106        if (entity.getContentType() != null) {
107            logger.debug("Content-Type: {}", entity.getContentType().getValue());
108            logger.debug("Content-Length: {}", entity.getContentLength());
109        }
110        InputStream in             = entity.getContent();
111        org.apache.http.Header enc = entity.getContentEncoding();
112        if (enc != null) {
113            final String encoding = enc.getValue();
114            logger.debug("Content-Encoding: {}", encoding);
115            if (ENCODING_GZIP.equalsIgnoreCase(encoding)) {
116                in = new GZIPInputStream(in);
117            } else if (ENCODING_DEFLATE.equalsIgnoreCase(encoding)) {
118                in = new InflaterInputStream(in, new Inflater(true));
119            } else {
120                throw new HarvesterException("Unsupported content encoding " +
121                        "in HTTP response: " + encoding);
122            }
123        }
124        this.stream = new CountingInputStream(job.wrap(in));
125        this.reader = harvester.createReader(stream);
126        readStart(OAI_NS, "OAI-PMH", true);
127        readContent(OAI_NS, "responseDate", true);
128        readStart(OAI_NS, "request", true, true);
129        readEnd(OAI_NS, "request", true);
130
131        List<ProtocolError> errors = null;
132        while (readStart(OAI_NS, "error", false, true)) {
133            if (errors == null) {
134                errors = new ArrayList<ProtocolError>();
135            }
136            final String code = reader.getAttributeValue(null, "code");
137            reader.next(); // skip start element after reading attrs
138            final String msg = readString(true);
139            readEnd(OAI_NS, "error");
140            errors.add(new ProtocolError(code, msg));
141        }
142        if (errors != null) {
143            throw new HarvesterProtocolErrorException(errors);
144        }
145    }
146
147
148    public void close() throws XMLStreamException {
149        readEnd(OAI_NS, "OAI-PMH");
150    }
151
152
153    public void release(HarvestJobImpl job) {
154        job.finishRequest(stream.getByteCount(),
155                (System.currentTimeMillis() - now));
156        try {
157            reader.close();
158        } catch (XMLStreamException e) {
159            /* IGNORE */
160        }
161        try {
162            stream.close();
163        } catch (IOException e) {
164            /* IGNORE */
165        }
166
167        /*
168         * try hard to release HTTP client resources ...
169         */
170        try {
171            EntityUtils.consume(response.getEntity());
172        } catch (IOException ex) {
173            /* IGNORE */
174        }
175
176        /* make sure to release allocated resources */
177        HttpClientUtils.closeQuietly(response);
178    }
179
180
181    XMLStreamReader getXMLStreamReader() {
182        return reader;
183    }
184
185
186    Location getLocation() {
187        return reader.getLocation();
188    }
189
190
191    boolean readStart(String namespaceURI, String localName, boolean required)
192            throws XMLStreamException {
193        return readStart(namespaceURI, localName, required, false);
194    }
195
196
197    boolean readStart(String namespaceURI, String localName, boolean required,
198            boolean attributes) throws XMLStreamException {
199        // System.err.println("readStart (" + localName + ", required = " +
200        // required + ") @ " + toReadable(reader));
201        if (!reader.isEndElement()) {
202            while (reader.hasNext()) {
203                // System.err.println("  LOOP: " + dumpState());
204                if (reader.isWhiteSpace()) {
205                    reader.next();
206                    continue;
207                }
208                if (reader.isStartElement()) {
209                    if (namespaceURI.equals(reader.getNamespaceURI()) &&
210                            localName.equals(reader.getLocalName())) {
211                        // System.err.print("--> found ");
212                        if (!attributes) {
213                            // System.err.print("and consumed ");
214                            reader.next(); // skip to next event
215                        }
216                        // System.err.println("@ " + toReadable(reader));
217                        return true;
218                    }
219                    break;
220                }
221                if (reader.isCharacters() || reader.isEndElement()) {
222                    break;
223                }
224                reader.next();
225            } // while
226        }
227        if (required) {
228            // System.err.println("--> error, not found @ " +
229            // toReadable(reader));
230            String what;
231            if (reader.isStartElement() || reader.isEndElement() ||
232                    reader.isEmptyElement()) {
233                what = "'" + reader.getName().toString() + "'";
234            } else {
235                what = "some character data";
236            }
237            throw new XMLStreamException("expected element '" +
238                    new QName(namespaceURI, localName) + "', but found "+
239                    what, reader.getLocation());
240        }
241        // System.err.println("--> not found @ " + toReadable(reader));
242        return false;
243    }
244
245
246    void readEnd(String namespaceURI, String localName)
247            throws XMLStreamException {
248        readEnd(namespaceURI, localName, false);
249    }
250
251
252    void readEnd(String namespaceURI, String localName, boolean skipContent)
253            throws XMLStreamException {
254        // System.err.println("readEnd (" + localName + ") @ " + dumpState() +
255        // ", skipContent = " + skipContent);
256        int level = 1;
257        while (reader.hasNext()) {
258            // System.err.println("  LOOP " + dumpState() + " [" +
259            // level + "]");
260            if (reader.isWhiteSpace()) {
261                reader.next();
262                continue;
263            }
264            if (skipContent) {
265                if (reader.isCharacters()) {
266                    reader.next();
267                    continue;
268                }
269                if (reader.isStartElement()) {
270                    if (!(namespaceURI.equals(reader.getNamespaceURI()) &&
271                            localName.equals(reader.getLocalName()))) {
272                        level++;
273                    }
274                    reader.next();
275                    continue;
276                }
277            }
278            if (reader.isEndElement()) {
279                level--;
280                // System.err.println("   @END-TAG: " + dumpState() + " [" +
281                // level + "]");
282                if (level == 0) {
283                    if (namespaceURI.equals(reader.getNamespaceURI()) &&
284                            localName.equals(reader.getLocalName())) {
285                        reader.next(); // consume tag
286                        break;
287                    } else {
288                        String what;
289                        if (reader.isStartElement() || reader.isEndElement() ||
290                                reader.isEmptyElement()) {
291                            what = "'" + reader.getName().toString() + "'";
292                        } else {
293                            what = "some character data";
294                        }
295                        throw new XMLStreamException("expected end tag for '" +
296                                new QName(namespaceURI, localName) +
297                                "', but found " + what,
298                                reader.getLocation());
299                    }
300                }
301            }
302            reader.next();
303        }
304        // System.err.println("--> ok @ " + dumpState());
305    }
306
307
308    boolean peekStart(String namespaceURI, String localName)
309            throws XMLStreamException {
310        if (reader.isWhiteSpace()) {
311            consumeWhitespace();
312        }
313        if (!reader.isStartElement()) {
314            return false;
315        }
316        return namespaceURI.equals(reader.getNamespaceURI()) &&
317                localName.equals(reader.getLocalName());
318    }
319
320
321    String readContent(String namespaceURI, String localName, boolean required)
322            throws XMLStreamException {
323        return readContent(namespaceURI, localName, required, true);
324    }
325
326
327    String readContent(String namespaceURI, String localName, boolean required,
328            boolean contentRequired) throws XMLStreamException {
329        String result = null;
330        if (readStart(namespaceURI, localName, required)) {
331            try {
332                result = readString(contentRequired);
333                if (!contentRequired && (result == null)) {
334                    result = "";
335                }
336            } catch (XMLStreamException e) {
337                StringBuilder sb = new StringBuilder();
338                sb.append("element '");
339                sb.append(new QName(namespaceURI, localName));
340                sb.append("' may not be empty");
341                throw new XMLStreamException(sb.toString(), e.getLocation());
342            }
343            readEnd(namespaceURI, localName);
344        }
345        return result;
346    }
347
348
349    int readContent(String namespaceURI, String localName, boolean required,
350            int defaultValue) throws XMLStreamException {
351        if (readStart(namespaceURI, localName, required)) {
352            String s = readString(true);
353            try {
354                readEnd(namespaceURI, localName);
355            } catch (XMLStreamException e) {
356                StringBuilder sb = new StringBuilder();
357                sb.append("element '");
358                sb.append(new QName(namespaceURI, localName));
359                sb.append(localName).append("' may not be empty");
360                throw new XMLStreamException(sb.toString(), e.getLocation());
361            }
362            try {
363                return Integer.parseInt(s);
364            } catch (NumberFormatException e) {
365                StringBuilder sb = new StringBuilder();
366                sb.append("element '");
367                sb.append(new QName(namespaceURI, localName));
368                sb.append("' was expected to be of type xs:integer; ");
369                sb.append("incompatible value was: ");
370                sb.append(s);
371                throw new XMLStreamException(sb.toString(),
372                        reader.getLocation(), e);
373            }
374        }
375        return defaultValue;
376    }
377
378
379    String readString(boolean required) throws XMLStreamException {
380        // System.err.println("readString @ " + toReadable(reader));
381        StringBuilder sb = new StringBuilder();
382        while (reader.isCharacters()) {
383            String s = reader.getText();
384            if (s != null) {
385                sb.append(s);
386            }
387            if (!reader.hasNext()) {
388                break;
389            }
390            reader.next();
391        } // while
392        String s = null;
393        if (sb.length() > 0) {
394            s = sb.toString().trim();
395        }
396        if (required && ((s == null) || s.isEmpty())) {
397            throw new XMLStreamException("expected character content "
398                    + "at position ", reader.getLocation());
399        }
400        // System.err.println("--> ok @ " + toReadable(reader));
401        return s;
402    }
403
404
405    String readAttributeValue(String namespaceURI, String localName)
406            throws XMLStreamException {
407        if (!reader.isStartElement()) {
408            throw new XMLStreamException("not at a start elment event",
409                    reader.getLocation());
410        }
411        String attr = reader.getAttributeValue(namespaceURI, localName);
412        if (attr != null) {
413            attr = attr.trim().intern();
414        }
415        return attr;
416    }
417
418
419    String readNamespaceURI() throws XMLStreamException {
420        if (!reader.isStartElement()) {
421            throw new XMLStreamException("not at a start elment event",
422                    reader.getLocation());
423        }
424        return reader.getNamespaceURI();
425    }
426
427
428    String peekElementLocalName() throws XMLStreamException {
429        if (!reader.isStartElement()) {
430            throw new XMLStreamException("not at a start elment event",
431                    reader.getLocation());
432        }
433        return reader.getLocalName();
434    }
435
436
437    void consumeStart() throws XMLStreamException {
438        if (!reader.isStartElement()) {
439            throw new XMLStreamException("not at a start elment event",
440                    reader.getLocation());
441        }
442        reader.next();
443    }
444
445
446    void consumeWhitespace() throws XMLStreamException {
447        outer:
448        while (reader.hasNext()) {
449            /*
450             * some hackish method to also skip stray UTF-8 BOMs; only works
451             * where whitespace is expected
452             */
453            if (reader.isCharacters()) {
454                final char buffer[] = reader.getTextCharacters();
455                final int end = reader.getTextStart() + reader.getTextLength();
456                for (int i = reader.getTextStart(); i < end; i++) {
457                    if (!(Character.isWhitespace(buffer[i]) ||
458                            (buffer[i] == 0x0FEFF))) {
459                        break outer;
460                    }
461                }
462            } else if (!reader.isWhiteSpace()) {
463                break outer;
464            }
465            reader.next();
466        } // while
467    }
468
469
470    void copyTo(XMLStreamWriter writer) throws XMLStreamException {
471        final int depth = reader.getDepth();
472        do {
473            copyEvent(reader, writer);
474            reader.next();
475        } while (reader.getDepth() >= depth);
476    }
477
478
479    private static void copyEvent(XMLStreamReader from, XMLStreamWriter to)
480            throws XMLStreamException {
481        switch (from.getEventType()) {
482        case XMLStreamConstants.START_DOCUMENT:
483            {
484                String version = from.getVersion();
485                if (version == null || version.length() == 0) {
486                    to.writeStartDocument();
487                } else {
488                    to.writeStartDocument(from.getCharacterEncodingScheme(),
489                            from.getVersion());
490                }
491                to.writeCharacters("\n");
492            }
493            return;
494
495        case XMLStreamConstants.END_DOCUMENT:
496            to.writeCharacters("\n");
497            to.writeEndDocument();
498            return;
499
500        case XMLStreamConstants.START_ELEMENT:
501            copyStartElement(from, to);
502            return;
503
504        case XMLStreamConstants.END_ELEMENT:
505            to.writeEndElement();
506            return;
507
508        case XMLStreamConstants.SPACE:
509            to.writeCharacters(from.getTextCharacters(), from.getTextStart(),
510                    from.getTextLength());
511            return;
512
513        case XMLStreamConstants.CDATA:
514            to.writeCData(from.getText());
515            return;
516
517        case XMLStreamConstants.CHARACTERS:
518            to.writeCharacters(from.getTextCharacters(), from.getTextStart(),
519                    from.getTextLength());
520            return;
521
522        case XMLStreamConstants.COMMENT:
523            to.writeComment(from.getText());
524            return;
525
526        case XMLStreamConstants.PROCESSING_INSTRUCTION:
527            to.writeProcessingInstruction(from.getPITarget(), from.getPIData());
528            return;
529
530        case XMLStreamConstants.DTD:
531        case XMLStreamConstants.ENTITY_REFERENCE:
532        case XMLStreamConstants.ATTRIBUTE:
533        case XMLStreamConstants.NAMESPACE:
534        case XMLStreamConstants.ENTITY_DECLARATION:
535        case XMLStreamConstants.NOTATION_DECLARATION:
536            /* FALL_TROUGH */
537        }
538        throw new XMLStreamException("unsupported event type: " +
539                from.getEventType());
540    }
541
542
543    private static void copyStartElement(XMLStreamReader from,
544            XMLStreamWriter to) throws XMLStreamException {
545        final int nsCount = from.getNamespaceCount();
546        if (nsCount > 0) { // yup, got some...
547            for (int i = 0; i < nsCount; ++i) {
548                String pfx = from.getNamespacePrefix(i);
549                String uri = from.getNamespaceURI(i);
550                if ((pfx == null) || pfx.isEmpty()) { // default NS
551                    to.setDefaultNamespace(uri);
552                } else {
553                    to.setPrefix(pfx, uri);
554                }
555            }
556        }
557
558        final String prefix = from.getPrefix();
559        final NamespaceContext from_ctx = from.getNamespaceContext();
560        final NamespaceContext to_ctx = to.getNamespaceContext();
561        boolean repair_prefix_namespace = false;
562        if ((prefix != null) && (to_ctx.getNamespaceURI(prefix) == null)) {
563            repair_prefix_namespace = true;
564            to.setPrefix(prefix, from_ctx.getNamespaceURI(prefix));
565        }
566
567        to.writeStartElement(prefix, from.getLocalName(),
568                from.getNamespaceURI());
569
570        if (nsCount > 0) {
571            // write namespace declarations
572            for (int i = 0; i < nsCount; ++i) {
573                String pfx = from.getNamespacePrefix(i);
574                String uri = from.getNamespaceURI(i);
575
576                if ((pfx == null) || pfx.isEmpty()) { // default NS
577                    to.writeDefaultNamespace(uri);
578                } else {
579                    to.writeNamespace(pfx, uri);
580                }
581            }
582        }
583        if (repair_prefix_namespace) {
584            to.writeNamespace(prefix, from_ctx.getNamespaceURI(prefix));
585        }
586
587        int attrCount = from.getAttributeCount();
588        if (attrCount > 0) {
589            for (int i = 0; i < attrCount; ++i) {
590                to.writeAttribute(from.getAttributePrefix(i),
591                        from.getAttributeNamespace(i),
592                        from.getAttributeLocalName(i),
593                        from.getAttributeValue(i));
594            }
595        }
596    }
597
598
599    String dumpState() {
600        StringBuilder sb = new StringBuilder();
601        switch (reader.getEventType()) {
602        case XMLStreamConstants.START_DOCUMENT:
603            return "START_DOC[";
604        case XMLStreamConstants.END_DOCUMENT:
605            return "END_DOC[";
606        case XMLStreamConstants.START_ELEMENT:
607            sb.append("START[");
608            sb.append(reader.getNamespaceURI());
609            sb.append(",");
610            sb.append(reader.getLocalName());
611            break;
612        case XMLStreamConstants.END_ELEMENT:
613            sb.append("END[");
614            sb.append(reader.getNamespaceURI());
615            sb.append(",");
616            sb.append(reader.getLocalName());
617            break;
618        case XMLStreamConstants.CHARACTERS:
619            sb.append("CHARACTERS[\"");
620            sb.append(reader.getText()
621                    .replace("\n", "\\n")
622                    .replace("\r", "\\r")
623                    .replace("\t", "\\t"));
624            sb.append("\", isWhitespace = ");
625            sb.append(reader.isWhiteSpace());
626            break;
627        case XMLStreamConstants.CDATA:
628            sb.append("CDATA[\"");
629            sb.append(reader.getText().replace("\n", "\\n")
630                    .replace("\r", "\\r").replace("\t", "\\t"));
631            sb.append("\", isWhitespace = ");
632            sb.append(reader.isWhiteSpace());
633            break;
634        default:
635            sb.append(Integer.toString(reader.getEventType()));
636        }
637        Location location = reader.getLocation();
638        sb.append(", row = ").append(location.getLineNumber());
639        sb.append(", col = ").append(location.getColumnNumber());
640        sb.append(", offset = ").append(location.getCharacterOffset());
641        sb.append("]");
642        return sb.toString();
643    }
644
645} // class XmlStreamReaderWrapper
Note: See TracBrowser for help on using the repository browser.