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

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