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

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