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

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