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

Last change on this file since 2128 was 2128, checked in by oschonef, 12 years ago
  • change exception message
  • Property svn:eol-style set to native
File size: 44.2 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("performing 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(
451                        "unknown host: " + uri.getHost(), e);
452            } catch (IOException e) {
453                String msg = null;
454                if ((e.getMessage() != null) && !e.getMessage().isEmpty()) {
455                    msg = e.getMessage();
456                }
457                throw new SRUClientException(msg != null
458                        ? msg
459                        : "input/output error", e);
460            }
461        } catch (SRUClientException e) {
462            /*
463             * if an error occurred, make sure we are freeing up the resources
464             * we've used
465             */
466            if (response != null) {
467                try {
468                    EntityUtils.consume(response.getEntity());
469                } catch (IOException ex) {
470                    /* IGNORE */
471                }
472            }
473            if (request != null) {
474                request.abort();
475            }
476            throw e;
477        }
478    }
479
480
481    private void parseExplainResponse(final SRUXMLStreamReader reader,
482            final SRUAbstractRequest request, final SRUExplainHandler handler)
483            throws SRUClientException {
484        logger.debug("parsing 'explain' response");
485        try {
486            // explainResponse
487            reader.readStart(SRU_NS, "explainResponse", true);
488
489            // explainResponse/version
490            SRUVersion version = parseVersion(reader);
491            logger.debug("version = {}, requested = {}",
492                    version, request.getVersionPerformed());
493
494            // explainResponse/record
495            reader.readStart(SRU_NS, "record", true);
496
497            String schema = reader.readContent(SRU_NS, "recordSchema", true);
498
499            SRURecordPacking packing = parseRecordPacking(reader, strictMode);
500
501            logger.debug("schema = {}, packing = {}", schema, packing);
502
503            // explainResponse/record/recordData
504            reader.readStart(SRU_NS, "recordData", true);
505            reader.readEnd(SRU_NS, "recordData", true);
506
507            // explainResponse/record/recordPosition
508            if (reader.readStart(SRU_NS, "recordPosition", false)) {
509                reader.readEnd(SRU_NS, "recordPosition", true);
510            }
511
512            // explainResponse/record/extraRecordData
513            if (reader.readStart(SRU_NS, "extraRecordData", false)) {
514                reader.readEnd(SRU_NS, "extraRecordData", true);
515            }
516
517            reader.readEnd(SRU_NS, "record");
518
519            // explainResponse/echoedExplainRequest
520            if (reader.readStart(SRU_NS, "echoedExplainRequest", false)) {
521                reader.readEnd(SRU_NS, "echoedExplainRequest", true);
522            }
523
524            /*
525             * common error: echoedExplainRequest in default namespace
526             */
527            if (reader.readStart("", "echoedExplainRequest", false)) {
528                logger.error("Element 'echoedExplainRequest' must be in SRU namespace, but endpoint put it into default namespace");
529                if (strictMode) {
530                    throw new SRUClientException("Element 'echoedExplainRequest' must be in SRU namespace, but endpoint put it into default namespace");
531                }
532                reader.readEnd("", "echoedExplainRequest", true);
533            }
534
535            // explainResponse/diagnostics
536            final List<SRUDiagnostic> diagnostics = parseDiagnostics(reader);
537            if (diagnostics != null) {
538                handler.onDiagnostics(diagnostics);
539            }
540
541            // explainResponse/extraResponseData
542            if (reader.readStart(SRU_NS, "extraResponseData", false)) {
543                reader.consumeWhitespace();
544                proxy.reset(reader);
545                try {
546                    handler.onExtraResponseData(proxy);
547                } catch (XMLStreamException e) {
548                    throw new SRUClientException("handler triggered "
549                            + "error while parsing 'extraResponseData'", e);
550                }
551                reader.consumeWhitespace();
552                reader.readEnd(SRU_NS, "extraResponseData", true);
553            }
554
555            reader.readEnd(SRU_NS, "explainResponse");
556        } catch (XMLStreamException e) {
557            throw new SRUClientException(e.getMessage(), e);
558        }
559    }
560
561
562    private void parseScanResponse(final SRUXMLStreamReader reader,
563            final SRUScanRequest request, final SRUScanHandler handler)
564            throws SRUClientException {
565        try {
566            /*
567             * if the endpoint cannot determine the operation, it should create
568             * a explain response.
569             */
570            if (reader.peekStart(SRU_NS, "explainResponse")) {
571                parseExplainResponse(reader, request, new SRUExplainHandler() {
572                    @Override
573                    public void onRequestStatistics(int bytes, long millisTotal,
574                            long millisNetwork, long millisParsing) {
575                    }
576
577
578                    @Override
579                    public void onExtraResponseData(XMLStreamReader reader)
580                            throws XMLStreamException, SRUClientException {
581                    }
582
583
584                    @Override
585                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
586                            throws SRUClientException {
587                        handler.onDiagnostics(diagnostics);
588                    }
589                });
590            } else {
591                logger.debug("parsing 'scanResponse' response");
592
593                // scanResponse
594                reader.readStart(SRU_NS, "scanResponse", true);
595
596                // scanResponse/version
597                SRUVersion version = parseVersion(reader);
598                logger.debug("version = {}, requested = {}", version,
599                        request.getVersionPerformed());
600
601                // scanResponse/terms
602                if (reader.readStart(SRU_NS, "terms", false)) {
603                    boolean first = true;
604                    while (reader.readStart(SRU_NS, "term", first)) {
605                        if (first) {
606                            first = false;
607                            handler.onStartTerms();
608                        }
609
610                        // scanResponse/terms/value
611                        String value = reader
612                                .readContent(SRU_NS, "value", true);
613
614                        // scanResponse/terms/numberOfRecords
615                        int numberOfRecords = reader.readContent(SRU_NS,
616                                "numberOfRecords", false, -1);
617
618                        // scanResponse/terms/displayTerm
619                        String displayTerm = reader.readContent(SRU_NS,
620                                "displayTerm", false);
621
622                        // scanResponse/terms/whereInList
623                        String s = reader.readContent(SRU_NS,
624                                "whereInList", false);
625                        WhereInList whereInList = null;
626                        if (s != null) {
627                            if ("first".equals(s)) {
628                                whereInList = WhereInList.FIRST;
629                            } else if ("last".equals(s)) {
630                                whereInList = WhereInList.LAST;
631                            } else if ("only".equals(s)) {
632                                whereInList = WhereInList.ONLY;
633                            } else if ("inner".equals(s)) {
634                                whereInList = WhereInList.INNER;
635                            } else {
636                                throw new SRUClientException(
637                                        "invalid value for 'whereInList': " + s);
638                            }
639                        }
640                        logger.debug("value = {}, numberOfRecords = {}, "
641                                + "displayTerm = {}, whereInList = {}",
642                                new Object[] { value, numberOfRecords,
643                                        displayTerm, whereInList });
644                        handler.onTerm(value, numberOfRecords, displayTerm,
645                                whereInList);
646
647                        // scanResponse/terms/extraTermData
648                        if (reader.readStart(SRU_NS, "extraTermData", first)) {
649                            reader.consumeWhitespace();
650                            proxy.reset(reader);
651                            try {
652                                handler.onExtraTermData(value, proxy);
653                            } catch (XMLStreamException e) {
654                                throw new SRUClientException("handler "
655                                        + "triggered error while parsing "
656                                        + "'extraTermData'", e);
657                            }
658                            reader.consumeWhitespace();
659                            reader.readEnd(SRU_NS, "extraTermData", true);
660                        }
661                        reader.readEnd(SRU_NS, "term", true);
662
663                    } // while
664                    reader.readEnd(SRU_NS, "terms");
665                    handler.onFinishTerms();
666                }
667
668                // scanResponse/echoedScanRequest
669                if (reader.readStart(SRU_NS, "echoedScanRequest", false)) {
670                    reader.readEnd(SRU_NS, "echoedScanRequest", true);
671                }
672
673                /*
674                 * common error: echoedScanRequest in default namespace
675                 */
676                if (reader.readStart("", "echoedScanRequest", false)) {
677                    logger.error("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
678                    if (strictMode) {
679                        throw new SRUClientException("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
680                    }
681                    reader.readEnd("", "echoedScanRequest", true);
682                }
683
684                // scanResponse/diagnostics
685                final List<SRUDiagnostic> diagnostics = parseDiagnostics(reader);
686                if (diagnostics != null) {
687                    handler.onDiagnostics(diagnostics);
688                }
689
690                // scanResponse/extraResponseData
691                if (reader.readStart(SRU_NS, "extraResponseData", false)) {
692                    reader.consumeWhitespace();
693                    proxy.reset(reader);
694                    try {
695                        handler.onExtraResponseData(proxy);
696                    } catch (XMLStreamException e) {
697                        throw new SRUClientException("handler triggered "
698                                + "error while parsing 'extraResponseData'", e);
699                    }
700                    reader.consumeWhitespace();
701                    reader.readEnd(SRU_NS, "extraResponseData", true);
702                }
703
704                reader.readEnd(SRU_NS, "scanResponse");
705            }
706        } catch (XMLStreamException e) {
707            throw new SRUClientException(e.getMessage(), e);
708        }
709    }
710
711
712    private void parseSearchRetrieveResponse(final SRUXMLStreamReader reader,
713            final SRUSearchRetrieveRequest request,
714            final SRUSearchRetrieveHandler handler) throws SRUClientException {
715        try {
716            /*
717             * if the endpoint cannot determine the operation, it should create
718             * a explain response.
719             */
720            if (reader.peekStart(SRU_NS, "explainResponse")) {
721                parseExplainResponse(reader, request, new SRUExplainHandler() {
722                    @Override
723                    public void onRequestStatistics(int bytes, long millisTotal,
724                            long millisNetwork, long millisParsing) {
725                    }
726
727
728                    @Override
729                    public void onExtraResponseData(XMLStreamReader reader)
730                            throws XMLStreamException, SRUClientException {
731                    }
732
733
734                    @Override
735                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
736                            throws SRUClientException {
737                        handler.onDiagnostics(diagnostics);
738                    }
739                });
740            } else {
741                logger.debug("parsing 'serarchRetrieve' response");
742
743                // searchRetrieveResponse
744                reader.readStart(SRU_NS, "searchRetrieveResponse", true);
745
746                // searchRetrieveResponse/version
747                SRUVersion version = parseVersion(reader);
748                logger.debug("version = {}, requested = {}", version,
749                        request.getVersionPerformed());
750
751                // searchRetrieveResponse/numberOfRecords
752                int numberOfRecords = reader.readContent(SRU_NS,
753                        "numberOfRecords", true, -1);
754
755                // searchRetrieveResponse/resultSetId
756                int resultSetId = reader.readContent(SRU_NS,
757                        "resultSetId", false, -1);
758
759                // searchRetrieveResponse/resultSetIdleTime
760                int resultSetIdleTime = reader.readContent(SRU_NS,
761                        "resultSetIdleTime", false, -1);
762
763                logger.debug("numberOfRecords = {}, resultSetId = {}, "
764                        + "resultSetIdleTime = {}", new Object[] {
765                        numberOfRecords, resultSetId, resultSetIdleTime });
766
767                // searchRetrieveResponse/results
768                if (numberOfRecords > 0) {
769                    reader.readStart(SRU_NS, "records", true);
770
771                    // searchRetrieveResponse/records/record
772                    boolean first = true;
773                    while (reader.readStart(SRU_NS, "record", first)) {
774                        if (first) {
775                            first = false;
776                            handler.onStartRecords(numberOfRecords,
777                                    resultSetId, resultSetIdleTime);
778                        }
779
780                        String schema = reader.readContent(SRU_NS,
781                                "recordSchema", true);
782
783                        SRURecordPacking packing =
784                                parseRecordPacking(reader, strictMode);
785
786                        logger.debug("schema = {}, packing = {}, " +
787                                "requested packing = {}",
788                                new Object[] { schema, packing,
789                                        request.getRecordPacking() });
790
791                        if ((request.getRecordPacking() != null) &&
792                                (packing != request.getRecordPacking())) {
793                            final SRURecordPacking p =
794                                    request.getRecordPacking();
795                            logger.error("requested '{}' record packing, but " +
796                                "server responded with '{}' record packing",
797                                    p.getStringValue(),
798                                    packing.getStringValue());
799                            if (strictMode) {
800                                throw new SRUClientException("requested '" +
801                                        p.getStringValue() +
802                                        "' record packing, but server " +
803                                        "responded with '" +
804                                        packing.getStringValue() +
805                                        "' record packing");
806                            }
807                        }
808
809                        // searchRetrieveResponse/record/recordData
810                        reader.readStart(SRU_NS, "recordData", true);
811                        reader.consumeWhitespace();
812
813                        SRURecordData recordData = null;
814                        SRUDiagnostic surrogate = null;
815                        SRUXMLStreamReader recordReader = null;
816
817                        if (packing == SRURecordPacking.STRING) {
818                            /*
819                             * read content into temporary buffer and then use
820                             * a new XML reader to parse record data
821                             */
822                            final String data = reader.readString(true);
823                            InputStream in =
824                                    new ByteArrayInputStream(data.getBytes());
825                            // FIXME: namespace context?
826                            recordReader = createReader(in, false);
827                        } else {
828                            recordReader = reader;
829                        }
830
831                        if (SRU_DIAGNOSTIC_RECORD_SCHEMA.equals(schema)) {
832                            surrogate = parseDiagnostic(recordReader, true);
833                        } else {
834                            SRURecordDataParser parser = findParser(schema);
835                            if (parser != null) {
836                                try {
837                                    proxy.reset(recordReader);
838                                    recordData = parser.parse(proxy);
839                                } catch (XMLStreamException e) {
840                                    throw new SRUClientException(
841                                            "error parsing record", e);
842                                }
843                                if (recordData == null) {
844                                    // FIXME: handle this better? maybe throw?
845                                    logger.warn("parse did not correctly "
846                                            + "parse the record, will skip "
847                                            + "handler callback.");
848                                }
849                            } else {
850                                // FIXME: handle this better?
851                                logger.debug("no record parser found for schema '{}'",
852                                        schema);
853                            }
854                        }
855
856                        if (packing == SRURecordPacking.STRING) {
857                            recordReader.closeCompletly();
858                        }
859
860                        reader.consumeWhitespace();
861                        reader.readEnd(SRU_NS, "recordData", true);
862
863                        String identifier = null;
864                        if (version == SRUVersion.VERSION_1_2) {
865                            identifier = reader.readContent(SRU_NS,
866                                    "recordIdentifier", false);
867                        }
868
869                        int position = reader.readContent(SRU_NS,
870                                "recordPosition", false, -1);
871
872                        logger.debug("recordIdentifier = {}, recordPosition = {}",
873                                identifier, position);
874
875                        // notify handler
876                        if (surrogate != null) {
877                            handler.onSurrogateRecord(identifier,
878                                    position, surrogate);
879                        } else {
880                            if (recordData != null) {
881                                handler.onRecord(identifier,
882                                        position, recordData);
883                            }
884                        }
885
886                        if (reader.readStart(SRU_NS, "extraRecordData", false)) {
887                            reader.consumeWhitespace();
888                            proxy.reset(reader);
889                            try {
890                                handler.onExtraRecordData(identifier,
891                                        position, proxy);
892                            } catch (XMLStreamException e) {
893                                throw new SRUClientException("handler "
894                                        + "triggered error while parsing "
895                                        + "'extraRecordData'", e);
896                            }
897                            reader.consumeWhitespace();
898                            reader.readEnd(SRU_NS, "extraRecordData", true);
899                        }
900
901                        reader.readEnd(SRU_NS, "record");
902                    } // while
903                    reader.readEnd(SRU_NS, "records");
904                } else {
905                    /*
906                     * provide a better error format, if
907                     */
908                    if (reader.readStart(SRU_NS, "records", false)) {
909                        int bad = 0;
910                        while (reader.readStart(SRU_NS, "record", false)) {
911                            bad++;
912                            reader.readEnd(SRU_NS, "record", true);
913                        }
914                        reader.readEnd(SRU_NS, "records", true);
915                        if (bad == 0) {
916                            logger.error("endpoint declared 0 results, but " +
917                                    "response contained an empty 'records' " +
918                                    "element");
919                            if (strictMode) {
920                                throw new SRUClientException(
921                                        "endpoint declared 0 results, but response contained an empty 'records' element (behavior violates SRU specification)");
922                            }
923                        } else {
924                            logger.error("endpoint declared 0 results, but " +
925                                    "response contained an " + bad +
926                                    " record(s)");
927                            if (strictMode) {
928                            throw new SRUClientException("endpoint declared 0 results, but response containted " +
929                                    bad + " records (behavior may violate SRU specification)");
930                            }
931                        }
932                    }
933                }
934
935                int nextRecordPosition = reader.readContent(SRU_NS,
936                        "nextRecordPosition", false, -1);
937                logger.debug("nextRecordPosition = {}", nextRecordPosition);
938                handler.onFinishRecords(nextRecordPosition);
939
940                // searchRetrieveResponse/echoedSearchRetrieveResponse
941                if (reader.readStart(SRU_NS,
942                        "echoedSearchRetrieveRequest", false)) {
943                    reader.readEnd(SRU_NS, "echoedSearchRetrieveRequest", true);
944                }
945
946                /*
947                 * common error: echoedSearchRetrieveRequest in default namespace
948                 */
949                if (reader.readStart("", "echoedSearchRetrieveRequest", false)) {
950                    logger.error("Element 'echoedSearchRetrieveRequest' must be in SRU namespace, but endpoint put it into default namespace");
951                    if (strictMode) {
952                        throw new SRUClientException("Element 'echoedSearchRetrieveRequest' must be in SRU namespace, but endpoint put it into default namespace");
953                    }
954                    reader.readEnd("", "echoedSearchRetrieveRequest", true);
955                }
956
957                // searchRetrieveResponse/diagnostics
958                final List<SRUDiagnostic> diagnostics = parseDiagnostics(reader);
959                if (diagnostics != null) {
960                    handler.onDiagnostics(diagnostics);
961                }
962
963                // explainResponse/extraResponseData
964                if (reader.readStart(SRU_NS, "extraResponseData", false)) {
965                    reader.consumeWhitespace();
966                    proxy.reset(reader);
967                    try {
968                        handler.onExtraResponseData(proxy);
969                    } catch (XMLStreamException e) {
970                        throw new SRUClientException("handler triggered "
971                                + "error while parsing 'extraResponseData'", e);
972                    }
973                    reader.consumeWhitespace();
974                    reader.readEnd(SRU_NS, "extraResponseData", true);
975                }
976
977                reader.readEnd(SRU_NS, "searchRetrieveResponse");
978            }
979        } catch (XMLStreamException e) {
980            throw new SRUClientException(e.getMessage(), e);
981        }
982    }
983
984
985    private static SRUVersion parseVersion(SRUXMLStreamReader reader)
986        throws XMLStreamException, SRUClientException {
987        final String v = reader.readContent(SRU_NS, "version", true);
988        if (VERSION_1_1.equals(v)) {
989            return SRUVersion.VERSION_1_1;
990        } else if (VERSION_1_2.equals(v)) {
991            return SRUVersion.VERSION_1_2;
992        } else {
993            throw new SRUClientException("invalid value '" + v +
994                    "' for version (valid values are: '" + VERSION_1_1 +
995                    "' and '" + VERSION_1_2 + "')");
996        }
997    }
998
999
1000    private static List<SRUDiagnostic> parseDiagnostics(
1001            SRUXMLStreamReader reader) throws XMLStreamException,
1002            SRUClientException {
1003        if (reader.readStart(SRU_NS, "diagnostics", false)) {
1004            List<SRUDiagnostic> diagnostics = null;
1005
1006            SRUDiagnostic diagnostic = null;
1007            while ((diagnostic = parseDiagnostic(reader,
1008                    (diagnostics == null))) != null) {
1009                if (diagnostics == null) {
1010                    diagnostics = new ArrayList<SRUDiagnostic>();
1011                }
1012                diagnostics.add(diagnostic);
1013            } // while
1014            reader.readEnd(SRU_NS, "diagnostics");
1015            return diagnostics;
1016        } else {
1017            return null;
1018        }
1019    }
1020
1021
1022    private static SRUDiagnostic parseDiagnostic(SRUXMLStreamReader reader,
1023            boolean required) throws XMLStreamException, SRUClientException {
1024        if (reader.readStart(SRU_DIAGNOSIC_NS, "diagnostic", required)) {
1025
1026            // diagnostic/uri
1027            String uri = reader.readContent(SRU_DIAGNOSIC_NS, "uri", true);
1028
1029            // diagnostic/details
1030            String details =
1031                    reader.readContent(SRU_DIAGNOSIC_NS, "details", false);
1032
1033            // diagnostic/message
1034            String message =
1035                    reader.readContent(SRU_DIAGNOSIC_NS, "message", false);
1036
1037            reader.readEnd(SRU_DIAGNOSIC_NS, "diagnostic");
1038
1039            logger.debug("diagostic: uri={}, detail={}, message={}",
1040                    new Object[] { uri, details, message });
1041            return new SRUDiagnostic(uri, details, message);
1042        } else {
1043            return null;
1044        }
1045    }
1046
1047
1048    private static SRURecordPacking parseRecordPacking(
1049            SRUXMLStreamReader reader, boolean pedantic)
1050            throws XMLStreamException, SRUClientException {
1051        final String v = reader.readContent(SRU_NS, "recordPacking", true);
1052
1053        if (RECORD_PACKING_XML.equals(v)) {
1054            return SRURecordPacking.XML;
1055        } else if (RECORD_PACKING_STRING.equals(v)) {
1056            return SRURecordPacking.STRING;
1057        } else if (!pedantic && RECORD_PACKING_XML.equalsIgnoreCase(v)) {
1058            logger.error("invalid value '{}' for record packing, should be '{}'",
1059                         v, RECORD_PACKING_XML);
1060            return SRURecordPacking.XML;
1061        } else if (!pedantic && RECORD_PACKING_STRING.equalsIgnoreCase(v)) {
1062            logger.error("invalid value '{}' for record packing, should be '{}'",
1063                         v, RECORD_PACKING_STRING);
1064            return SRURecordPacking.STRING;
1065
1066        } else {
1067            throw new SRUClientException("invalid value '" + v +
1068                    "' for record packing (valid values are: '" +
1069                    RECORD_PACKING_XML + "' and '" + RECORD_PACKING_STRING +
1070                    "')");
1071        }
1072    }
1073
1074
1075    private SRURecordDataParser findParser(String schema) {
1076        SRURecordDataParser parser = parsers.get(schema);
1077        if (parser == null) {
1078            parser = parsers.get(RECORD_DATA_PARSER_SCHEMA_ANY);
1079        }
1080        return parser;
1081    }
1082
1083
1084    private SRUXMLStreamReader createReader(InputStream in, boolean wrap)
1085            throws XMLStreamException {
1086        return new SRUXMLStreamReader(in, wrap);
1087    }
1088
1089} // class SRUClient
Note: See TracBrowser for help on using the repository browser.