source: SRUClient/trunk/src/main/java/eu/clarin/sru/client/SRUClient.java @ 2114

Last change on this file since 2114 was 2114, checked in by oschonef, 12 years ago
  • add toggle to operate client in strict SRU protocol conformance mode and a quirks mode
  • do not raise fatal errors for certain errors when running in quicks mode
  • Property svn:eol-style set to native
File size: 44.0 KB
Line 
1/**
2 * This software is copyright (c) 2011 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.client;
18
19import java.io.ByteArrayInputStream;
20import java.io.IOException;
21import java.io.InputStream;
22import java.net.URI;
23import java.net.UnknownHostException;
24import java.util.ArrayList;
25import java.util.HashMap;
26import java.util.List;
27import java.util.Map;
28import java.util.concurrent.TimeUnit;
29
30import javax.xml.stream.XMLStreamException;
31import javax.xml.stream.XMLStreamReader;
32
33import org.apache.commons.lang.NullArgumentException;
34import org.apache.http.HttpEntity;
35import org.apache.http.HttpResponse;
36import org.apache.http.HttpStatus;
37import org.apache.http.StatusLine;
38import org.apache.http.client.ClientProtocolException;
39import org.apache.http.client.HttpClient;
40import org.apache.http.client.methods.HttpGet;
41import org.apache.http.impl.client.DefaultHttpClient;
42import org.apache.http.params.CoreProtocolPNames;
43import org.apache.http.util.EntityUtils;
44import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
47import eu.clarin.sru.client.SRUScanHandler.WhereInList;
48
49/**
50 * A class to perform SRU operations.
51 * <p>This class is <em>not</em> thread-safe!</p>
52 */
53public class SRUClient {
54    /** constant record data schema parser to match any schema */
55    public static final String RECORD_DATA_PARSER_SCHEMA_ANY = "*";
56    /** default version the client will use, if not otherwise specified */
57    public static final SRUVersion DEFAULT_SRU_VERSION = SRUVersion.VERSION_1_2;
58    private static final String SRU_NS =
59            "http://www.loc.gov/zing/srw/";
60    private static final String SRU_DIAGNOSIC_NS =
61            "http://www.loc.gov/zing/srw/diagnostic/";
62    private static final String SRU_DIAGNOSTIC_RECORD_SCHEMA =
63            "info:srw/schema/1/diagnostics-v1.1";
64    private static final String VERSION_1_1 = "1.1";
65    private static final String VERSION_1_2 = "1.2";
66    private static final String RECORD_PACKING_XML = "xml";
67    private static final String RECORD_PACKING_STRING = "string";
68    private static final Logger logger =
69            LoggerFactory.getLogger(SRUClient.class);
70    private final SRUVersion defaultVersion;
71    private final HttpClient httpClient;
72    private final Map<String, SRURecordDataParser> parsers =
73            new HashMap<String, SRURecordDataParser>();
74    private final XmlStreamReaderProxy proxy = new XmlStreamReaderProxy();
75    private boolean strictMode;
76
77
78    /**
79     * Constructor. This constructor will create a <em>strict</em> client and
80     * use the default SRU version.
81     *
82     * @see #SRUClient(SRUVersion, boolean)
83     * @see #DEFAULT_SRU_VERSION
84     */
85    public SRUClient() {
86        this(DEFAULT_SRU_VERSION, true);
87    }
88
89
90    /**
91     * Constructor. This constructor will create a <em>strict</em> client.
92     *
93     * @param defaultVersion
94     *            the default version to use for SRU requests; may be overridden
95     *            by individual requests
96     * @see #SRUClient(SRUVersion, boolean)
97     */
98    public SRUClient(SRUVersion defaultVersion) {
99        this(defaultVersion, true);
100    }
101
102
103    /**
104     * Constructor.
105     *
106     * @param defaultVersion
107     *            the default version to use for SRU requests; may be overridden
108     *            by individual requests
109     * @param strictMode
110     *            if <code>true</code> the client will strictly adhere to the
111     *            SRU standard and raise fatal errors on violations, if
112     *            <code>false</code> it will act more forgiving and ignore
113     *            certain violations
114     *
115     */
116    public SRUClient(SRUVersion defaultVersion, boolean strictMode) {
117        if (defaultVersion == null) {
118            throw new NullPointerException("version == null");
119        }
120        this.defaultVersion = defaultVersion;
121        this.strictMode = strictMode;
122        this.httpClient = new DefaultHttpClient();
123        this.httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT,
124                    "eu.clarin.sru.client/0.0.1");
125    }
126
127
128    /**
129     * Get the SRU protocol conformance mode of the client.
130     *
131     * @return <code>true</code> if the client operation in strict mode,
132     *         <code>false</code> otherwise
133     */
134    public boolean isStrictMode() {
135        return strictMode;
136    }
137
138
139    /**
140     * Set the SRU protocol conformance mode of the client.
141     *
142     * @param strictMode
143     *            <code>true</code> if the client should operate in strict mode,
144     *            <code>false</code> if the client should be more tolerant
145     */
146    public void setStrictMode(boolean strictMode) {
147        this.strictMode = strictMode;
148    }
149
150    /**
151     * Register a record data parser.
152     *
153     * @param parser
154     *            a parser instance
155     * @throws SRUClientException
156     *             if a parser handing the same record schema is already
157     *             registered
158     * @throws NullPointerException
159     *             if any required argument is <code>null</code>
160     * @throws IllegalArgumentException
161     *             if the supplied parser is invalid
162     */
163    public void registerRecordParser(SRURecordDataParser parser)
164            throws SRUClientException {
165        if (parser == null) {
166            throw new NullPointerException("parser == null");
167        }
168        final String recordSchema = parser.getRecordSchema();
169        if (recordSchema == null) {
170            throw new NullPointerException("parser.getRecordSchema() == null");
171        }
172        if (recordSchema.isEmpty()) {
173            throw new IllegalArgumentException(
174                    "parser.getRecordSchema() returns empty string");
175        }
176
177        if (!parsers.containsKey(recordSchema)) {
178            parsers.put(recordSchema, parser);
179        } else {
180            throw new SRUClientException(
181                    "record data parser already registered: " + recordSchema);
182        }
183    }
184
185
186    /**
187     * Perform a <em>explain</em> operation.
188     *
189     * @param request
190     *            an instance of a {@link SRUExplainRequest} object
191     * @param handler
192     *            an instance of {@link SRUExplainHandler} to receive callbacks
193     *            when processing the result of this request
194     * @throws SRUClientException
195     *             if an unrecoverable error occurred
196     * @throws NullPointerException
197     *             if any required argument is <code>null</code>
198     * @see SRUExplainRequest
199     * @see SRUExplainHandler
200     */
201    public void explain(SRUExplainRequest request, SRUExplainHandler handler)
202            throws SRUClientException {
203        if (request == null) {
204            throw new NullPointerException("request == null");
205        }
206        if (handler == null) {
207            throw new NullArgumentException("handler == null");
208        }
209        logger.debug("explain");
210
211        final long ts_start = System.nanoTime();
212
213        // create URI and perform request
214        final URI uri = request.makeURI(defaultVersion);
215        HttpResponse response = executeRequest(uri);
216        HttpEntity entity = response.getEntity();
217        if (entity == null) {
218            throw new SRUClientException("cannot get entity");
219        }
220
221        InputStream stream = null;
222        SRUXMLStreamReader reader = null;
223        try {
224            stream = entity.getContent();
225
226            final long ts_parsing = System.nanoTime();
227            reader = createReader(stream, true);
228            parseExplainResponse(reader, request, handler);
229            final long ts_end = System.nanoTime();
230
231            final long millisTotal =
232                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_start);
233            final long millisNetwork =
234                    TimeUnit.NANOSECONDS.toMillis(ts_parsing - ts_start);
235            final long millisParsing =
236                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_parsing);
237            logger.debug("{} byte(s) in {} milli(s) ({} milli(s) network / {} milli(s) parsing)",
238                    new Object[] { reader.getByteCount(),
239                            millisTotal, millisNetwork, millisParsing });
240            handler.onRequestStatistics((int) reader.getByteCount(),
241                    millisTotal, millisNetwork, millisParsing);
242        } catch (IllegalStateException e) {
243            throw new SRUClientException("error reading response", e);
244        } catch (IOException e) {
245            throw new SRUClientException("error reading response", e);
246        } catch (XMLStreamException e) {
247            throw new SRUClientException("error reading response", e);
248        } finally {
249            if (reader != null) {
250                try {
251                    reader.close();
252                } catch (XMLStreamException e) {
253                    /* IGNORE */
254                }
255            }
256            if (stream != null) {
257                try {
258                    stream.close();
259                } catch (IOException e) {
260                    /* IGNORE */
261                }
262            }
263        }
264    }
265
266
267    /**
268     * Perform a <em>scan</em> operation.
269     *
270     * @param request
271     *            an instance of a {@link SRUScanRequest} object
272     * @param handler
273     *            an instance of {@link SRUScanHandler} to receive callbacks
274     *            when processing the result of this request
275     * @throws SRUClientException
276     *             if an unrecoverable error occurred
277     * @throws NullPointerException
278     *             if any required argument is <code>null</code>
279     * @see SRUScanRequest
280     * @see SRUScanHandler
281     */
282    public void scan(SRUScanRequest request, SRUScanHandler handler)
283            throws SRUClientException {
284        if (request == null) {
285            throw new NullPointerException("request == null");
286        }
287        if (handler == null) {
288            throw new NullArgumentException("handler == null");
289        }
290        logger.debug("searchRetrieve: scanClause = {}", request.getScanClause());
291
292        final long ts_start = System.nanoTime();
293
294        // create URI and perform request
295        final URI uri = request.makeURI(defaultVersion);
296        HttpResponse response = executeRequest(uri);
297        HttpEntity entity = response.getEntity();
298        if (entity == null) {
299            throw new SRUClientException("cannot get entity");
300        }
301
302        InputStream stream = null;
303        SRUXMLStreamReader reader = null;
304        try {
305            stream = entity.getContent();
306
307            final long ts_parsing = System.nanoTime();
308            reader = createReader(stream, true);
309            parseScanResponse(reader, request, handler);
310            final long ts_end = System.nanoTime();
311
312            final long millisTotal =
313                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_start);
314            final long millisNetwork =
315                    TimeUnit.NANOSECONDS.toMillis(ts_parsing - ts_start);
316            final long millisParsing =
317                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_parsing);
318            logger.debug("{} byte(s) in {} milli(s) ({} milli(s) network / {} milli(s) parsing)",
319                    new Object[] { reader.getByteCount(),
320                            millisTotal, millisNetwork, millisParsing });
321            handler.onRequestStatistics((int) reader.getByteCount(),
322                    millisTotal, millisNetwork, millisParsing);
323        } catch (IllegalStateException e) {
324            throw new SRUClientException("error reading response", e);
325        } catch (IOException e) {
326            throw new SRUClientException("error reading response", e);
327        } catch (XMLStreamException e) {
328            throw new SRUClientException("error reading response", e);
329        } finally {
330            if (reader != null) {
331                try {
332                    reader.close();
333                } catch (XMLStreamException e) {
334                    /* IGNORE */
335                }
336            }
337            if (stream != null) {
338                try {
339                    stream.close();
340                } catch (IOException e) {
341                    /* IGNORE */
342                }
343            }
344        }
345    }
346
347
348    /**
349     * Perform a <em>searchRetreive</em> operation.
350     *
351     * @param request
352     *            an instance of a {@link SRUSearchRetrieveRequest} object
353     * @param handler
354     *            an instance of {@link SRUSearchRetrieveHandler} to receive
355     *            callbacks when processing the result of this request
356     * @throws SRUClientException
357     *             if an unrecoverable error occurred
358     * @throws NullPointerException
359     *             if any required argument is <code>null</code>
360     * @see SRUSearchRetrieveRequest
361     * @see SRUSearchRetrieveHandler
362     */
363    public void searchRetrieve(SRUSearchRetrieveRequest request,
364            SRUSearchRetrieveHandler handler) throws SRUClientException {
365        if (request == null) {
366            throw new NullPointerException("request == null");
367        }
368        if (handler == null) {
369            throw new NullArgumentException("handler == null");
370        }
371        logger.debug("searchRetrieve: query = {}", request.getQuery());
372
373        final long ts_start = System.nanoTime();
374
375        // create URI and perform request
376        final URI uri = request.makeURI(defaultVersion);
377        HttpResponse response = executeRequest(uri);
378        HttpEntity entity = response.getEntity();
379        if (entity == null) {
380            throw new SRUClientException("cannot get entity");
381        }
382
383        InputStream stream = null;
384        SRUXMLStreamReader reader = null;
385        try {
386            stream = entity.getContent();
387
388            final long ts_parsing = System.nanoTime();
389            reader = createReader(stream, true);
390            parseSearchRetrieveResponse(reader, request, handler);
391            final long ts_end = System.nanoTime();
392
393            final long millisTotal =
394                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_start);
395            final long millisNetwork =
396                    TimeUnit.NANOSECONDS.toMillis(ts_parsing - ts_start);
397            final long millisParsing =
398                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_parsing);
399            logger.debug("{} byte(s) in {} milli(s) ({} milli(s) network / {} milli(s) parsing)",
400                    new Object[] { reader.getByteCount(),
401                            millisTotal, millisNetwork, millisParsing });
402            handler.onRequestStatistics((int) reader.getByteCount(),
403                    millisTotal, millisNetwork, millisParsing);
404        } catch (IllegalStateException e) {
405            throw new SRUClientException("error reading response", e);
406        } catch (IOException e) {
407            throw new SRUClientException("error reading response", e);
408        } catch (XMLStreamException e) {
409            throw new SRUClientException("error reading response", e);
410        } finally {
411            if (reader != null) {
412                try {
413                    reader.close();
414                } catch (XMLStreamException e) {
415                    /* IGNORE */
416                }
417            }
418            if (stream != null) {
419                try {
420                    stream.close();
421                } catch (IOException e) {
422                    /* IGNORE */
423                }
424            }
425        }
426    }
427
428
429    private HttpResponse executeRequest(URI uri) throws SRUClientException {
430        HttpGet request = null;
431        HttpResponse response = null;
432        try {
433            logger.debug("executing HTTP request: {}", uri.toString());
434            try {
435                request = new HttpGet(uri);
436                response = httpClient.execute(request);
437                StatusLine status = response.getStatusLine();
438                if (status.getStatusCode() != HttpStatus.SC_OK) {
439                    if (status.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
440                        throw new SRUClientException("not found: " + uri);
441                    } else {
442                        throw new SRUClientException("unexpected status: " +
443                                status.getStatusCode());
444                    }
445                }
446                return response;
447            } catch (ClientProtocolException e) {
448                throw new SRUClientException("client protocol exception", e);
449            } catch (UnknownHostException e) {
450                throw new SRUClientException("unknown host: " + uri.getHost(),
451                        e);
452            } catch (IOException e) {
453                throw new SRUClientException("input/output error", e);
454            }
455        } catch (SRUClientException e) {
456            /*
457             * if an error occurred, make sure we are freeing up the resources
458             * we've used
459             */
460            if (response != null) {
461                try {
462                    EntityUtils.consume(response.getEntity());
463                } catch (IOException ex) {
464                    /* IGNORE */
465                }
466            }
467            if (request != null) {
468                request.abort();
469            }
470            throw e;
471        }
472    }
473
474
475    private void parseExplainResponse(final SRUXMLStreamReader reader,
476            final SRUAbstractRequest request, final SRUExplainHandler handler)
477            throws SRUClientException {
478        logger.debug("parsing 'explain' response");
479        try {
480            // explainResponse
481            reader.readStart(SRU_NS, "explainResponse", true);
482
483            // explainResponse/version
484            SRUVersion version = parseVersion(reader);
485            logger.debug("version = {}, requested = {}",
486                    version, request.getVersionPerformed());
487
488            // explainResponse/record
489            reader.readStart(SRU_NS, "record", true);
490
491            String schema = reader.readContent(SRU_NS, "recordSchema", true);
492
493            SRURecordPacking packing = parseRecordPacking(reader, strictMode);
494
495            logger.debug("schema = {}, packing = {}", schema, packing);
496
497            // explainResponse/record/recordData
498            reader.readStart(SRU_NS, "recordData", true);
499            reader.readEnd(SRU_NS, "recordData", true);
500
501            // explainResponse/record/recordPosition
502            if (reader.readStart(SRU_NS, "recordPosition", false)) {
503                reader.readEnd(SRU_NS, "recordPosition", true);
504            }
505
506            // explainResponse/record/extraRecordData
507            if (reader.readStart(SRU_NS, "extraRecordData", false)) {
508                reader.readEnd(SRU_NS, "extraRecordData", true);
509            }
510
511            reader.readEnd(SRU_NS, "record");
512
513            // explainResponse/echoedExplainRequest
514            if (reader.readStart(SRU_NS, "echoedExplainRequest", false)) {
515                reader.readEnd(SRU_NS, "echoedExplainRequest", true);
516            }
517
518            /*
519             * common error: echoedExplainRequest in default namespace
520             */
521            if (reader.readStart("", "echoedExplainRequest", false)) {
522                logger.error("Element 'echoedExplainRequest' must be in SRU namespace, but endpoint put it into default namespace");
523                if (strictMode) {
524                    throw new SRUClientException("Element 'echoedExplainRequest' must be in SRU namespace, but endpoint put it into default namespace");
525                }
526                reader.readEnd("", "echoedExplainRequest", true);
527            }
528
529            // explainResponse/diagnostics
530            final List<SRUDiagnostic> diagnostics = parseDiagnostics(reader);
531            if (diagnostics != null) {
532                handler.onDiagnostics(diagnostics);
533            }
534
535            // explainResponse/extraResponseData
536            if (reader.readStart(SRU_NS, "extraResponseData", false)) {
537                reader.consumeWhitespace();
538                proxy.reset(reader);
539                try {
540                    handler.onExtraResponseData(proxy);
541                } catch (XMLStreamException e) {
542                    throw new SRUClientException("handler triggered "
543                            + "error while parsing 'extraResponseData'", e);
544                }
545                reader.consumeWhitespace();
546                reader.readEnd(SRU_NS, "extraResponseData", true);
547            }
548
549            reader.readEnd(SRU_NS, "explainResponse");
550        } catch (XMLStreamException e) {
551            throw new SRUClientException(e.getMessage(), e);
552        }
553    }
554
555
556    private void parseScanResponse(final SRUXMLStreamReader reader,
557            final SRUScanRequest request, final SRUScanHandler handler)
558            throws SRUClientException {
559        try {
560            /*
561             * if the endpoint cannot determine the operation, it should create
562             * a explain response.
563             */
564            if (reader.peekStart(SRU_NS, "explainResponse")) {
565                parseExplainResponse(reader, request, new SRUExplainHandler() {
566                    @Override
567                    public void onRequestStatistics(int bytes, long millisTotal,
568                            long millisNetwork, long millisParsing) {
569                    }
570
571
572                    @Override
573                    public void onExtraResponseData(XMLStreamReader reader)
574                            throws XMLStreamException, SRUClientException {
575                    }
576
577
578                    @Override
579                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
580                            throws SRUClientException {
581                        handler.onDiagnostics(diagnostics);
582                    }
583                });
584            } else {
585                logger.debug("parsing 'scanResponse' response");
586
587                // scanResponse
588                reader.readStart(SRU_NS, "scanResponse", true);
589
590                // scanResponse/version
591                SRUVersion version = parseVersion(reader);
592                logger.debug("version = {}, requested = {}", version,
593                        request.getVersionPerformed());
594
595                // scanResponse/terms
596                if (reader.readStart(SRU_NS, "terms", false)) {
597                    boolean first = true;
598                    while (reader.readStart(SRU_NS, "term", first)) {
599                        if (first) {
600                            first = false;
601                            handler.onStartTerms();
602                        }
603
604                        // scanResponse/terms/value
605                        String value = reader
606                                .readContent(SRU_NS, "value", true);
607
608                        // scanResponse/terms/numberOfRecords
609                        int numberOfRecords = reader.readContent(SRU_NS,
610                                "numberOfRecords", false, -1);
611
612                        // scanResponse/terms/displayTerm
613                        String displayTerm = reader.readContent(SRU_NS,
614                                "displayTerm", false);
615
616                        // scanResponse/terms/whereInList
617                        String s = reader.readContent(SRU_NS,
618                                "whereInList", false);
619                        WhereInList whereInList = null;
620                        if (s != null) {
621                            if ("first".equals(s)) {
622                                whereInList = WhereInList.FIRST;
623                            } else if ("last".equals(s)) {
624                                whereInList = WhereInList.LAST;
625                            } else if ("only".equals(s)) {
626                                whereInList = WhereInList.ONLY;
627                            } else if ("inner".equals(s)) {
628                                whereInList = WhereInList.INNER;
629                            } else {
630                                throw new SRUClientException(
631                                        "invalid value for 'whereInList': " + s);
632                            }
633                        }
634                        logger.debug("value = {}, numberOfRecords = {}, "
635                                + "displayTerm = {}, whereInList = {}",
636                                new Object[] { value, numberOfRecords,
637                                        displayTerm, whereInList });
638                        handler.onTerm(value, numberOfRecords, displayTerm,
639                                whereInList);
640
641                        // scanResponse/terms/extraTermData
642                        if (reader.readStart(SRU_NS, "extraTermData", first)) {
643                            reader.consumeWhitespace();
644                            proxy.reset(reader);
645                            try {
646                                handler.onExtraTermData(value, proxy);
647                            } catch (XMLStreamException e) {
648                                throw new SRUClientException("handler "
649                                        + "triggered error while parsing "
650                                        + "'extraTermData'", e);
651                            }
652                            reader.consumeWhitespace();
653                            reader.readEnd(SRU_NS, "extraTermData", true);
654                        }
655                        reader.readEnd(SRU_NS, "term", true);
656
657                    } // while
658                    reader.readEnd(SRU_NS, "terms");
659                    handler.onFinishTerms();
660                }
661
662                // scanResponse/echoedScanRequest
663                if (reader.readStart(SRU_NS, "echoedScanRequest", false)) {
664                    reader.readEnd(SRU_NS, "echoedScanRequest", true);
665                }
666
667                /*
668                 * common error: echoedScanRequest in default namespace
669                 */
670                if (reader.readStart("", "echoedScanRequest", false)) {
671                    logger.error("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
672                    if (strictMode) {
673                        throw new SRUClientException("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
674                    }
675                    reader.readEnd("", "echoedScanRequest", true);
676                }
677
678                // scanResponse/diagnostics
679                final List<SRUDiagnostic> diagnostics = parseDiagnostics(reader);
680                if (diagnostics != null) {
681                    handler.onDiagnostics(diagnostics);
682                }
683
684                // scanResponse/extraResponseData
685                if (reader.readStart(SRU_NS, "extraResponseData", false)) {
686                    reader.consumeWhitespace();
687                    proxy.reset(reader);
688                    try {
689                        handler.onExtraResponseData(proxy);
690                    } catch (XMLStreamException e) {
691                        throw new SRUClientException("handler triggered "
692                                + "error while parsing 'extraResponseData'", e);
693                    }
694                    reader.consumeWhitespace();
695                    reader.readEnd(SRU_NS, "extraResponseData", true);
696                }
697
698                reader.readEnd(SRU_NS, "scanResponse");
699            }
700        } catch (XMLStreamException e) {
701            throw new SRUClientException(e.getMessage(), e);
702        }
703    }
704
705
706    private void parseSearchRetrieveResponse(final SRUXMLStreamReader reader,
707            final SRUSearchRetrieveRequest request,
708            final SRUSearchRetrieveHandler handler) throws SRUClientException {
709        try {
710            /*
711             * if the endpoint cannot determine the operation, it should create
712             * a explain response.
713             */
714            if (reader.peekStart(SRU_NS, "explainResponse")) {
715                parseExplainResponse(reader, request, new SRUExplainHandler() {
716                    @Override
717                    public void onRequestStatistics(int bytes, long millisTotal,
718                            long millisNetwork, long millisParsing) {
719                    }
720
721
722                    @Override
723                    public void onExtraResponseData(XMLStreamReader reader)
724                            throws XMLStreamException, SRUClientException {
725                    }
726
727
728                    @Override
729                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
730                            throws SRUClientException {
731                        handler.onDiagnostics(diagnostics);
732                    }
733                });
734            } else {
735                logger.debug("parsing 'serarchRetrieve' response");
736
737                // searchRetrieveResponse
738                reader.readStart(SRU_NS, "searchRetrieveResponse", true);
739
740                // searchRetrieveResponse/version
741                SRUVersion version = parseVersion(reader);
742                logger.debug("version = {}, requested = {}", version,
743                        request.getVersionPerformed());
744
745                // searchRetrieveResponse/numberOfRecords
746                int numberOfRecords = reader.readContent(SRU_NS,
747                        "numberOfRecords", true, -1);
748
749                // searchRetrieveResponse/resultSetId
750                int resultSetId = reader.readContent(SRU_NS,
751                        "resultSetId", false, -1);
752
753                // searchRetrieveResponse/resultSetIdleTime
754                int resultSetIdleTime = reader.readContent(SRU_NS,
755                        "resultSetIdleTime", false, -1);
756
757                logger.debug("numberOfRecords = {}, resultSetId = {}, "
758                        + "resultSetIdleTime = {}", new Object[] {
759                        numberOfRecords, resultSetId, resultSetIdleTime });
760
761                // searchRetrieveResponse/results
762                if (numberOfRecords > 0) {
763                    reader.readStart(SRU_NS, "records", true);
764
765                    // searchRetrieveResponse/records/record
766                    boolean first = true;
767                    while (reader.readStart(SRU_NS, "record", first)) {
768                        if (first) {
769                            first = false;
770                            handler.onStartRecords(numberOfRecords,
771                                    resultSetId, resultSetIdleTime);
772                        }
773
774                        String schema = reader.readContent(SRU_NS,
775                                "recordSchema", true);
776
777                        SRURecordPacking packing =
778                                parseRecordPacking(reader, strictMode);
779
780                        logger.debug("schema = {}, packing = {}, " +
781                                "requested packing = {}",
782                                new Object[] { schema, packing,
783                                        request.getRecordPacking() });
784
785                        if ((request.getRecordPacking() != null) &&
786                                (packing != request.getRecordPacking())) {
787                            final SRURecordPacking p =
788                                    request.getRecordPacking();
789                            logger.error("requested '{}' record packing, but " +
790                                "server responded with '{}' record packing",
791                                    p.getStringValue(),
792                                    packing.getStringValue());
793                            if (strictMode) {
794                                throw new SRUClientException("requested '" +
795                                        p.getStringValue() +
796                                        "' record packing, but server " +
797                                        "responded with '" +
798                                        packing.getStringValue() +
799                                        "' record packing");
800                            }
801                        }
802
803                        // searchRetrieveResponse/record/recordData
804                        reader.readStart(SRU_NS, "recordData", true);
805                        reader.consumeWhitespace();
806
807                        SRURecordData recordData = null;
808                        SRUDiagnostic surrogate = null;
809                        SRUXMLStreamReader recordReader = null;
810
811                        if (packing == SRURecordPacking.STRING) {
812                            /*
813                             * read content into temporary buffer and then use
814                             * a new XML reader to parse record data
815                             */
816                            final String data = reader.readString(true);
817                            InputStream in =
818                                    new ByteArrayInputStream(data.getBytes());
819                            // FIXME: namespace context?
820                            recordReader = createReader(in, false);
821                        } else {
822                            recordReader = reader;
823                        }
824
825                        if (SRU_DIAGNOSTIC_RECORD_SCHEMA.equals(schema)) {
826                            surrogate = parseDiagnostic(recordReader, true);
827                        } else {
828                            SRURecordDataParser parser = findParser(schema);
829                            if (parser != null) {
830                                try {
831                                    proxy.reset(recordReader);
832                                    recordData = parser.parse(proxy);
833                                } catch (XMLStreamException e) {
834                                    throw new SRUClientException(
835                                            "error parsing record", e);
836                                }
837                                if (recordData == null) {
838                                    // FIXME: handle this better? maybe throw?
839                                    logger.warn("parse did not correctly "
840                                            + "parse the record, will skip "
841                                            + "handler callback.");
842                                }
843                            } else {
844                                // FIXME: handle this better?
845                                logger.debug("no record parser found for schema '{}'",
846                                        schema);
847                            }
848                        }
849
850                        if (packing == SRURecordPacking.STRING) {
851                            recordReader.closeCompletly();
852                        }
853
854                        reader.consumeWhitespace();
855                        reader.readEnd(SRU_NS, "recordData", true);
856
857                        String identifier = null;
858                        if (version == SRUVersion.VERSION_1_2) {
859                            identifier = reader.readContent(SRU_NS,
860                                    "recordIdentifier", false);
861                        }
862
863                        int position = reader.readContent(SRU_NS,
864                                "recordPosition", false, -1);
865
866                        logger.debug("recordIdentifier = {}, recordPosition = {}",
867                                identifier, position);
868
869                        // notify handler
870                        if (surrogate != null) {
871                            handler.onSurrogateRecord(identifier,
872                                    position, surrogate);
873                        } else {
874                            if (recordData != null) {
875                                handler.onRecord(identifier,
876                                        position, recordData);
877                            }
878                        }
879
880                        if (reader.readStart(SRU_NS, "extraRecordData", false)) {
881                            reader.consumeWhitespace();
882                            proxy.reset(reader);
883                            try {
884                                handler.onExtraRecordData(identifier,
885                                        position, proxy);
886                            } catch (XMLStreamException e) {
887                                throw new SRUClientException("handler "
888                                        + "triggered error while parsing "
889                                        + "'extraRecordData'", e);
890                            }
891                            reader.consumeWhitespace();
892                            reader.readEnd(SRU_NS, "extraRecordData", true);
893                        }
894
895                        reader.readEnd(SRU_NS, "record");
896                    } // while
897                    reader.readEnd(SRU_NS, "records");
898                } else {
899                    /*
900                     * provide a better error format, if
901                     */
902                    if (reader.readStart(SRU_NS, "records", false)) {
903                        int bad = 0;
904                        while (reader.readStart(SRU_NS, "record", false)) {
905                            bad++;
906                            reader.readEnd(SRU_NS, "record", true);
907                        }
908                        reader.readEnd(SRU_NS, "records", true);
909                        if (bad == 0) {
910                            logger.error("endpoint declared 0 results, but " +
911                                    "response contained an empty 'records' " +
912                                    "element");
913                            if (strictMode) {
914                                throw new SRUClientException(
915                                        "endpoint declared 0 results, but response contained an empty 'records' element (behavior violates SRU specification)");
916                            }
917                        } else {
918                            logger.error("endpoint declared 0 results, but " +
919                                    "response contained an " + bad +
920                                    " record(s)");
921                            if (strictMode) {
922                            throw new SRUClientException("endpoint declared 0 results, but response containted " +
923                                    bad + " records (behavior may violate SRU specification)");
924                            }
925                        }
926                    }
927                }
928
929                int nextRecordPosition = reader.readContent(SRU_NS,
930                        "nextRecordPosition", false, -1);
931                logger.debug("nextRecordPosition = {}", nextRecordPosition);
932                handler.onFinishRecords(nextRecordPosition);
933
934                // searchRetrieveResponse/echoedSearchRetrieveResponse
935                if (reader.readStart(SRU_NS,
936                        "echoedSearchRetrieveRequest", false)) {
937                    reader.readEnd(SRU_NS, "echoedSearchRetrieveRequest", true);
938                }
939
940                /*
941                 * common error: echoedSearchRetrieveRequest in default namespace
942                 */
943                if (reader.readStart("", "echoedSearchRetrieveRequest", false)) {
944                    logger.error("Element 'echoedSearchRetrieveRequest' must be in SRU namespace, but endpoint put it into default namespace");
945                    if (strictMode) {
946                        throw new SRUClientException("Element 'echoedSearchRetrieveRequest' must be in SRU namespace, but endpoint put it into default namespace");
947                    }
948                    reader.readEnd("", "echoedSearchRetrieveRequest", true);
949                }
950
951                // searchRetrieveResponse/diagnostics
952                final List<SRUDiagnostic> diagnostics = parseDiagnostics(reader);
953                if (diagnostics != null) {
954                    handler.onDiagnostics(diagnostics);
955                }
956
957                // explainResponse/extraResponseData
958                if (reader.readStart(SRU_NS, "extraResponseData", false)) {
959                    reader.consumeWhitespace();
960                    proxy.reset(reader);
961                    try {
962                        handler.onExtraResponseData(proxy);
963                    } catch (XMLStreamException e) {
964                        throw new SRUClientException("handler triggered "
965                                + "error while parsing 'extraResponseData'", e);
966                    }
967                    reader.consumeWhitespace();
968                    reader.readEnd(SRU_NS, "extraResponseData", true);
969                }
970
971                reader.readEnd(SRU_NS, "searchRetrieveResponse");
972            }
973        } catch (XMLStreamException e) {
974            throw new SRUClientException(e.getMessage(), e);
975        }
976    }
977
978
979    private static SRUVersion parseVersion(SRUXMLStreamReader reader)
980        throws XMLStreamException, SRUClientException {
981        final String v = reader.readContent(SRU_NS, "version", true);
982        if (VERSION_1_1.equals(v)) {
983            return SRUVersion.VERSION_1_1;
984        } else if (VERSION_1_2.equals(v)) {
985            return SRUVersion.VERSION_1_2;
986        } else {
987            throw new SRUClientException("invalid value '" + v +
988                    "' for version (valid values are: '" + VERSION_1_1 +
989                    "' and '" + VERSION_1_2 + "')");
990        }
991    }
992
993
994    private static List<SRUDiagnostic> parseDiagnostics(
995            SRUXMLStreamReader reader) throws XMLStreamException,
996            SRUClientException {
997        if (reader.readStart(SRU_NS, "diagnostics", false)) {
998            List<SRUDiagnostic> diagnostics = null;
999
1000            SRUDiagnostic diagnostic = null;
1001            while ((diagnostic = parseDiagnostic(reader,
1002                    (diagnostics == null))) != null) {
1003                if (diagnostics == null) {
1004                    diagnostics = new ArrayList<SRUDiagnostic>();
1005                }
1006                diagnostics.add(diagnostic);
1007            } // while
1008            reader.readEnd(SRU_NS, "diagnostics");
1009            return diagnostics;
1010        } else {
1011            return null;
1012        }
1013    }
1014
1015
1016    private static SRUDiagnostic parseDiagnostic(SRUXMLStreamReader reader,
1017            boolean required) throws XMLStreamException, SRUClientException {
1018        if (reader.readStart(SRU_DIAGNOSIC_NS, "diagnostic", required)) {
1019
1020            // diagnostic/uri
1021            String uri = reader.readContent(SRU_DIAGNOSIC_NS, "uri", true);
1022
1023            // diagnostic/details
1024            String details =
1025                    reader.readContent(SRU_DIAGNOSIC_NS, "details", false);
1026
1027            // diagnostic/message
1028            String message =
1029                    reader.readContent(SRU_DIAGNOSIC_NS, "message", false);
1030
1031            reader.readEnd(SRU_DIAGNOSIC_NS, "diagnostic");
1032
1033            logger.debug("diagostic: uri={}, detail={}, message={}",
1034                    new Object[] { uri, details, message });
1035            return new SRUDiagnostic(uri, details, message);
1036        } else {
1037            return null;
1038        }
1039    }
1040
1041
1042    private static SRURecordPacking parseRecordPacking(
1043            SRUXMLStreamReader reader, boolean pedantic)
1044            throws XMLStreamException, SRUClientException {
1045        final String v = reader.readContent(SRU_NS, "recordPacking", true);
1046
1047        if (RECORD_PACKING_XML.equals(v)) {
1048            return SRURecordPacking.XML;
1049        } else if (RECORD_PACKING_STRING.equals(v)) {
1050            return SRURecordPacking.STRING;
1051        } else if (!pedantic && RECORD_PACKING_XML.equalsIgnoreCase(v)) {
1052            logger.error("invalid value '{}' for record packing, should be '{}'",
1053                         v, RECORD_PACKING_XML);
1054            return SRURecordPacking.XML;
1055        } else if (!pedantic && RECORD_PACKING_STRING.equalsIgnoreCase(v)) {
1056            logger.error("invalid value '{}' for record packing, should be '{}'",
1057                         v, RECORD_PACKING_STRING);
1058            return SRURecordPacking.STRING;
1059
1060        } else {
1061            throw new SRUClientException("invalid value '" + v +
1062                    "' for record packing (valid values are: '" +
1063                    RECORD_PACKING_XML + "' and '" + RECORD_PACKING_STRING +
1064                    "')");
1065        }
1066    }
1067
1068
1069    private SRURecordDataParser findParser(String schema) {
1070        SRURecordDataParser parser = parsers.get(schema);
1071        if (parser == null) {
1072            parser = parsers.get(RECORD_DATA_PARSER_SCHEMA_ANY);
1073        }
1074        return parser;
1075    }
1076
1077
1078    private SRUXMLStreamReader createReader(InputStream in, boolean wrap)
1079            throws XMLStreamException {
1080        return new SRUXMLStreamReader(in, wrap);
1081    }
1082
1083} // class SRUClient
Note: See TracBrowser for help on using the repository browser.