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

Last change on this file since 2956 was 2956, checked in by oschonef, 11 years ago
  • more work on explain record data parser
  • Property svn:eol-style set to native
File size: 57.0 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                try {
559                    proxy.reset(recordReader);
560                    /*
561                     * Try to parse explain record data with explain record
562                     * parser. It will throw an exception, if it cannot handle
563                     * the data.
564                     */
565                    recordData = explainRecordParser.parse(proxy,
566                            version, strictMode, schema);
567                } catch (XMLStreamException e) {
568                    throw new SRUClientException(
569                            "error parsing explain record", e);
570                } finally {
571                    /*
572                     * make sure, we're deallocate the record reader in case of
573                     * string record packing
574                     */
575                    if (packing == SRURecordPacking.STRING) {
576                        recordReader.closeCompletly();
577                    }
578                }
579                if (recordData == null) {
580                    // FIXME: error message
581                    throw new SRUClientException(
582                            "error parsing explain record");
583                }
584                reader.consumeWhitespace();
585                reader.readEnd(SRU_NS, "recordData", true);
586
587                if (version == SRUVersion.VERSION_1_2) {
588                    reader.readContent(SRU_NS, "recordIdentifier", false);
589                }
590                reader.readContent(SRU_NS, "recordPosition", false, -1);
591
592                // notify handler
593                handler.onRecord(null, -1, recordData);
594
595                if (reader.readStart(SRU_NS, "extraRecordData", false)) {
596                    reader.consumeWhitespace();
597                    proxy.reset(reader);
598                    try {
599                        handler.onExtraRecordData(null, -1, proxy);
600                    } catch (XMLStreamException e) {
601                        throw new SRUClientException("handler "
602                                + "triggered error while parsing "
603                                + "'extraRecordData'", e);
604                    }
605                    reader.consumeWhitespace();
606                    reader.readEnd(SRU_NS, "extraRecordData", true);
607                }
608
609                handler.onFinishRecords(-1);
610
611                reader.readEnd(SRU_NS, "record");
612            } else {
613                /*
614                 * do not really parse record and skip everything
615                 * until <record> end tag
616                 */
617                reader.readEnd(SRU_NS, "record", true);
618            }
619
620            // explainResponse/echoedExplainRequest
621            if (reader.readStart(SRU_NS, "echoedExplainRequest", false)) {
622                reader.readEnd(SRU_NS, "echoedExplainRequest", true);
623            }
624
625            /*
626             * common error: echoedExplainRequest in default namespace
627             */
628            if (reader.readStart("", "echoedExplainRequest", false)) {
629                logger.error("Element 'echoedExplainRequest' must be in SRU " +
630                        "namespace, but endpoint put it into default namespace");
631                if (strictMode) {
632                    throw new SRUClientException("Element " +
633                            "'echoedExplainRequest' must be in SRU namespace,"+
634                            " but endpoint put it into default namespace");
635                }
636                reader.readEnd("", "echoedExplainRequest", true);
637            }
638
639            // explainResponse/diagnostics
640            final List<SRUDiagnostic> diagnostics =
641                    parseDiagnostics(reader, strictMode);
642            if (diagnostics != null) {
643                handler.onDiagnostics(diagnostics);
644            }
645
646            // explainResponse/extraResponseData
647            if (reader.readStart(SRU_NS, "extraResponseData", false)) {
648                reader.consumeWhitespace();
649                proxy.reset(reader);
650                try {
651                    handler.onExtraResponseData(proxy);
652                } catch (XMLStreamException e) {
653                    throw new SRUClientException("handler triggered "
654                            + "error while parsing 'extraResponseData'", e);
655                }
656                reader.consumeWhitespace();
657                reader.readEnd(SRU_NS, "extraResponseData", true);
658            }
659
660            reader.readEnd(SRU_NS, "explainResponse");
661        } catch (XMLStreamException e) {
662            throw new SRUClientException(e.getMessage(), e);
663        }
664    }
665
666
667    private void parseScanResponse(final SRUXMLStreamReader reader,
668            final SRUScanRequest request, final SRUScanHandler handler)
669            throws SRUClientException {
670        try {
671            /*
672             * if the endpoint cannot determine the operation, it should create
673             * a explain response.
674             */
675            if (reader.peekStart(SRU_NS, "explainResponse")) {
676                doParseExplainResponse(reader, request, new SRUExplainHandler() {
677                    @Override
678                    public void onRequestStatistics(int bytes, long millisTotal,
679                            long millisNetwork, long millisParsing) {
680                    }
681
682
683                    @Override
684                    public void onExtraResponseData(XMLStreamReader reader)
685                            throws XMLStreamException, SRUClientException {
686                    }
687
688
689                    @Override
690                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
691                            throws SRUClientException {
692                        handler.onDiagnostics(diagnostics);
693                    }
694
695
696                    @Override
697                    public void onStartRecords(int numberOfRecords,
698                            String resultSetId, int resultSetIdleTime)
699                            throws SRUClientException {
700                    }
701
702
703                    @Override
704                    public void onFinishRecords(int nextRecordPosition)
705                            throws SRUClientException {
706                    }
707
708
709                    @Override
710                    public void onRecord(String identifier, int position,
711                            SRURecordData data) throws SRUClientException {
712                    }
713
714
715                    @Override
716                    public void onExtraRecordData(String identifier,
717                            int position, XMLStreamReader reader)
718                            throws XMLStreamException, SRUClientException {
719                    }
720                }, false);
721            } else {
722                final boolean strictMode = request.isStrictMode();
723
724                logger.debug("parsing 'scan' response (mode = {})",
725                        (strictMode ? "strict" : "non-strict"));
726
727
728                // scanResponse
729                reader.readStart(SRU_NS, "scanResponse", true);
730
731                // scanResponse/version
732                SRUVersion version = parseVersion(reader);
733                logger.debug("version = {}, requested = {}", version,
734                        request.getVersionPerformed());
735
736                // scanResponse/terms
737                if (reader.readStart(SRU_NS, "terms", false)) {
738                    boolean first = true;
739                    while (reader.readStart(SRU_NS, "term", first)) {
740                        if (first) {
741                            first = false;
742                            handler.onStartTerms();
743                        }
744
745                        // scanResponse/terms/value
746                        String value =
747                                reader.readContent(SRU_NS, "value", true);
748
749                        // scanResponse/terms/numberOfRecords
750                        int numberOfRecords = reader.readContent(SRU_NS,
751                                "numberOfRecords", false, -1);
752
753                        // scanResponse/terms/displayTerm
754                        String displayTerm = reader.readContent(SRU_NS,
755                                "displayTerm", false);
756
757                        // scanResponse/terms/whereInList
758                        String s = reader.readContent(SRU_NS,
759                                "whereInList", false);
760                        SRUWhereInList whereInList = null;
761                        if (s != null) {
762                            if ("first".equals(s)) {
763                                whereInList = SRUWhereInList.FIRST;
764                            } else if ("last".equals(s)) {
765                                whereInList = SRUWhereInList.LAST;
766                            } else if ("only".equals(s)) {
767                                whereInList = SRUWhereInList.ONLY;
768                            } else if ("inner".equals(s)) {
769                                whereInList = SRUWhereInList.INNER;
770                            } else {
771                                throw new SRUClientException(
772                                        "invalid value for 'whereInList': " + s);
773                            }
774                        }
775                        logger.debug("value = {}, numberOfRecords = {}, " +
776                                "displayTerm = {}, whereInList = {}", value,
777                                numberOfRecords, displayTerm, whereInList);
778                        handler.onTerm(value, numberOfRecords, displayTerm,
779                                whereInList);
780
781                        // scanResponse/terms/extraTermData
782                        if (reader.readStart(SRU_NS, "extraTermData", first)) {
783                            reader.consumeWhitespace();
784                            proxy.reset(reader);
785                            try {
786                                handler.onExtraTermData(value, proxy);
787                            } catch (XMLStreamException e) {
788                                throw new SRUClientException("handler "
789                                        + "triggered error while parsing "
790                                        + "'extraTermData'", e);
791                            }
792                            reader.consumeWhitespace();
793                            reader.readEnd(SRU_NS, "extraTermData", true);
794                        }
795                        reader.readEnd(SRU_NS, "term", true);
796
797                    } // while
798                    reader.readEnd(SRU_NS, "terms");
799                    handler.onFinishTerms();
800                }
801
802                // scanResponse/echoedScanRequest
803                if (reader.readStart(SRU_NS, "echoedScanRequest", false)) {
804                    reader.readEnd(SRU_NS, "echoedScanRequest", true);
805                }
806
807                /*
808                 * common error: echoedScanRequest in default namespace
809                 */
810                if (reader.readStart("", "echoedScanRequest", false)) {
811                    logger.error("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
812                    if (strictMode) {
813                        throw new SRUClientException("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
814                    }
815                    reader.readEnd("", "echoedScanRequest", true);
816                }
817
818                // scanResponse/diagnostics
819                final List<SRUDiagnostic> diagnostics =
820                        parseDiagnostics(reader, strictMode);
821                if (diagnostics != null) {
822                    handler.onDiagnostics(diagnostics);
823                }
824
825                // scanResponse/extraResponseData
826                if (reader.readStart(SRU_NS, "extraResponseData", false)) {
827                    reader.consumeWhitespace();
828                    proxy.reset(reader);
829                    try {
830                        handler.onExtraResponseData(proxy);
831                    } catch (XMLStreamException e) {
832                        throw new SRUClientException("handler triggered "
833                                + "error while parsing 'extraResponseData'", e);
834                    }
835                    reader.consumeWhitespace();
836                    reader.readEnd(SRU_NS, "extraResponseData", true);
837                }
838
839                reader.readEnd(SRU_NS, "scanResponse");
840            }
841        } catch (XMLStreamException e) {
842            throw new SRUClientException(e.getMessage(), e);
843        }
844    }
845
846
847    private void parseSearchRetrieveResponse(final SRUXMLStreamReader reader,
848            final SRUSearchRetrieveRequest request,
849            final SRUSearchRetrieveHandler handler) throws SRUClientException {
850        try {
851            /*
852             * if the endpoint cannot determine the operation, it should create
853             * a explain response.
854             */
855            if (reader.peekStart(SRU_NS, "explainResponse")) {
856                doParseExplainResponse(reader, request, new SRUExplainHandler() {
857                    @Override
858                    public void onRequestStatistics(int bytes, long millisTotal,
859                            long millisNetwork, long millisParsing) {
860                    }
861
862
863                    @Override
864                    public void onExtraResponseData(XMLStreamReader reader)
865                            throws XMLStreamException, SRUClientException {
866                    }
867
868
869                    @Override
870                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
871                            throws SRUClientException {
872                        handler.onDiagnostics(diagnostics);
873                    }
874
875
876                    @Override
877                    public void onStartRecords(int numberOfRecords,
878                            String resultSetId, int resultSetIdleTime)
879                            throws SRUClientException {
880                    }
881
882
883                    @Override
884                    public void onFinishRecords(int nextRecordPosition)
885                            throws SRUClientException {
886                    }
887
888
889                    @Override
890                    public void onRecord(String identifier, int position,
891                            SRURecordData data) throws SRUClientException {
892                    }
893
894
895                    @Override
896                    public void onExtraRecordData(String identifier,
897                            int position, XMLStreamReader reader)
898                            throws XMLStreamException, SRUClientException {
899                    }
900                }, false);
901            } else {
902                final boolean strictMode = request.isStrictMode();
903
904                logger.debug("parsing 'searchRetrieve' response (mode = {}",
905                        (strictMode ? "strict" : "non-strict"));
906
907                // searchRetrieveResponse
908                reader.readStart(SRU_NS, "searchRetrieveResponse", true);
909
910                // searchRetrieveResponse/version
911                SRUVersion version = parseVersion(reader);
912                logger.debug("version = {}, requested = {}", version,
913                        request.getVersionPerformed());
914
915                // searchRetrieveResponse/numberOfRecords
916                int numberOfRecords = reader.readContent(SRU_NS,
917                        "numberOfRecords", true, -1);
918
919                // searchRetrieveResponse/resultSetId
920                String resultSetId = reader.readContent(SRU_NS,
921                        "resultSetId", false);
922
923                // searchRetrieveResponse/resultSetIdleTime
924                int resultSetIdleTime = reader.readContent(SRU_NS,
925                        "resultSetIdleTime", false, -1);
926
927                logger.debug("numberOfRecords = {}, resultSetId = {}, " +
928                        "resultSetIdleTime = {}", numberOfRecords,
929                        resultSetId, resultSetIdleTime);
930
931                // searchRetrieveResponse/results
932                if (numberOfRecords > 0) {
933                    /*
934                     * some endpoints set numberOfRecords but do not serialize
935                     * any records, e.g if requested record schema is not
936                     * supported
937                     */
938                    int recordCount = 0;
939                    if (reader.readStart(SRU_NS, "records", false)) {
940                        // searchRetrieveResponse/records/record
941                        boolean first = true;
942                        while (reader.readStart(SRU_NS, "record", first)) {
943                            if (first) {
944                                first = false;
945                                handler.onStartRecords(numberOfRecords,
946                                        resultSetId, resultSetIdleTime);
947                            }
948
949                            /*
950                             * common error: recordPacking before recordSchema
951                             */
952                            SRURecordPacking packing = null;
953                            if (!strictMode &&
954                                    reader.peekStart(SRU_NS, "recordPacking")) {
955                                packing = parseRecordPacking(reader, false);
956                                if (packing != null) {
957                                    logger.error("element <recordPacking> " +
958                                            "must appear after element " +
959                                            "<recordSchema> within " +
960                                            "element <record>");
961                                }
962                            }
963                            String schema = reader.readContent(SRU_NS,
964                                    "recordSchema", true);
965                            if (packing == null) {
966                                packing = parseRecordPacking(reader, strictMode);
967                            }
968
969                            logger.debug("schema = {}, packing = {}, " +
970                                    "requested packing = {}",
971                                    schema, packing,
972                                    request.getRecordPacking());
973
974                            if ((request.getRecordPacking() != null) &&
975                                    (packing != request.getRecordPacking())) {
976                                final SRURecordPacking p =
977                                        request.getRecordPacking();
978                                logger.error("requested '{}' record packing, " +
979                                        "but server responded with '{}' " +
980                                        "record packing",
981                                        p.getStringValue(),
982                                        packing.getStringValue());
983                                if (strictMode) {
984                                    throw new SRUClientException("requested '" +
985                                            p.getStringValue() +
986                                            "' record packing, but server " +
987                                            "responded with '" +
988                                            packing.getStringValue() +
989                                            "' record packing");
990                                }
991                            }
992
993                            // searchRetrieveResponse/record/recordData
994                            reader.readStart(SRU_NS, "recordData", true);
995                            reader.consumeWhitespace();
996
997                            SRURecordData recordData = null;
998                            SRUDiagnostic surrogate = null;
999                            SRUXMLStreamReader recordReader = null;
1000
1001                            if (packing == SRURecordPacking.STRING) {
1002                                /*
1003                                 * read content into temporary buffer and then
1004                                 * use a new XML reader to parse record data
1005                                 */
1006                                final String data = reader.readString(true);
1007                                InputStream in = new ByteArrayInputStream(
1008                                        data.getBytes());
1009                                // FIXME: namespace context?
1010                                recordReader = createReader(in, false);
1011                            } else {
1012                                recordReader = reader;
1013                            }
1014
1015                            if (SRU_DIAGNOSTIC_RECORD_SCHEMA.equals(schema)) {
1016                                surrogate = parseDiagnostic(recordReader, true,
1017                                        strictMode);
1018                            } else {
1019                                SRURecordDataParser parser = findParser(schema);
1020                                if (parser != null) {
1021                                    try {
1022                                        proxy.reset(recordReader);
1023                                        recordData = parser.parse(proxy);
1024                                    } catch (XMLStreamException e) {
1025                                        throw new SRUClientException(
1026                                                "error parsing record", e);
1027                                    } finally {
1028                                        /*
1029                                         * make sure, we deallocate the record
1030                                         * reader in case of string record
1031                                         * packing
1032                                         */
1033                                        if (packing == SRURecordPacking.STRING) {
1034                                            recordReader.closeCompletly();
1035                                        }
1036                                    }
1037                                    if (recordData == null) {
1038                                        logger.debug("record parser did not parse "
1039                                                + "record correctly and returned "
1040                                                + "null; injecting client side "
1041                                                + "surrogate diagnostic");
1042                                        surrogate = new SRUDiagnostic(
1043                                                SRUClientDiagnostics.DIAG_RECORD_PARSER_NULL,
1044                                                null,
1045                                                "Record parser for schema '" +
1046                                                        schema +
1047                                                        "' did not " +
1048                                                        "parse record correctly " +
1049                                                        "and errornously " +
1050                                                        "returned null.");
1051                                    }
1052                                } else {
1053                                    /*
1054                                     * no record parser found, inject a
1055                                     * surrogate diagnostic
1056                                     */
1057                                    logger.debug(
1058                                            "no record data parser found "
1059                                                    + "for schema '{}'; injecting client "
1060                                                    + "side surrogate diagnostic",
1061                                            schema);
1062                                    surrogate = new SRUDiagnostic(
1063                                            SRUClientDiagnostics.DIAG_NO_RECORD_PARSER,
1064                                            schema,
1065                                            "No record data parser for schema '" +
1066                                                    schema + "' found.");
1067                                }
1068                            }
1069
1070                            reader.consumeWhitespace();
1071                            reader.readEnd(SRU_NS, "recordData", true);
1072
1073                            String identifier = null;
1074                            if (version == SRUVersion.VERSION_1_2) {
1075                                identifier = reader.readContent(SRU_NS,
1076                                        "recordIdentifier", false);
1077                            }
1078
1079                            int position = reader.readContent(SRU_NS,
1080                                    "recordPosition", false, -1);
1081
1082                            logger.debug("recordIdentifier = {}, " +
1083                                    "recordPosition = {}",
1084                                    identifier, position);
1085
1086                            // notify handler
1087                            if (surrogate != null) {
1088                                handler.onSurrogateRecord(identifier,
1089                                        position, surrogate);
1090                            } else {
1091                                if (recordData != null) {
1092                                    handler.onRecord(identifier,
1093                                            position, recordData);
1094                                }
1095                            }
1096
1097                            if (reader.readStart(SRU_NS,
1098                                    "extraRecordData", false)) {
1099                                reader.consumeWhitespace();
1100                                proxy.reset(reader);
1101                                try {
1102                                    handler.onExtraRecordData(identifier,
1103                                            position, proxy);
1104                                } catch (XMLStreamException e) {
1105                                    throw new SRUClientException("handler " +
1106                                            "triggered error while parsing " +
1107                                            "'extraRecordData'", e);
1108                                }
1109                                reader.consumeWhitespace();
1110                                reader.readEnd(SRU_NS, "extraRecordData", true);
1111                            }
1112
1113                            reader.readEnd(SRU_NS, "record");
1114                            recordCount++;
1115                        } // while
1116                        reader.readEnd(SRU_NS, "records");
1117                    }
1118                    if (recordCount == 0) {
1119                        logger.error("endpoint declared {} results, but response contained no <record> elements (behavior may violate SRU specification)", numberOfRecords);
1120                    } else if ((request.getMaximumRecords() != -1) && (recordCount > request.getMaximumRecords())) {
1121                        logger.error("endpoint did not honour 'maximumRecords' request parameter and responded with {} records instead of a maximum of {}", recordCount, request.getMaximumRecords());
1122                    }
1123                } else {
1124                    /*
1125                     * provide a better error format, if endpoints responds with
1126                     * an empty <records> element
1127                     */
1128                    if (reader.readStart(SRU_NS, "records", false)) {
1129                        int bad = 0;
1130                        while (reader.readStart(SRU_NS, "record", false)) {
1131                            bad++;
1132                            reader.readEnd(SRU_NS, "record", true);
1133                        }
1134                        reader.readEnd(SRU_NS, "records", true);
1135                        if (bad == 0) {
1136                            logger.error("endpoint declared 0 results, but " +
1137                                    "response contained an empty 'records' " +
1138                                    "element");
1139                            if (strictMode) {
1140                                throw new SRUClientException(
1141                                        "endpoint declared 0 results, but " +
1142                                        "response contained an empty " +
1143                                        "'records' element (behavior " +
1144                                        "violates SRU specification)");
1145                            }
1146                        } else {
1147                            logger.error("endpoint declared 0 results, but " +
1148                                    "response contained " + bad +
1149                                    " record(s)");
1150                            if (strictMode) {
1151                                throw new SRUClientException(
1152                                            "endpoint declared 0 results, " +
1153                                            "but response containted " + bad +
1154                                            " records (behavior may violate " +
1155                                            "SRU specification)");
1156                            }
1157                        }
1158                    }
1159                }
1160
1161                int nextRecordPosition = reader.readContent(SRU_NS,
1162                        "nextRecordPosition", false, -1);
1163                logger.debug("nextRecordPosition = {}", nextRecordPosition);
1164                handler.onFinishRecords(nextRecordPosition);
1165
1166                // searchRetrieveResponse/echoedSearchRetrieveResponse
1167                if (reader.readStart(SRU_NS,
1168                        "echoedSearchRetrieveRequest", false)) {
1169                    reader.readEnd(SRU_NS, "echoedSearchRetrieveRequest", true);
1170                }
1171
1172                /*
1173                 * common error: echoedSearchRetrieveRequest in
1174                 * default namespace
1175                 */
1176                if (reader.readStart("", "echoedSearchRetrieveRequest", false)) {
1177                    logger.error("Element 'echoedSearchRetrieveRequest' " +
1178                            "must be in SRU namespace, but endpoint put it " +
1179                            "into default namespace");
1180                    if (strictMode) {
1181                        throw new SRUClientException(
1182                                "Element 'echoedSearchRetrieveRequest' must " +
1183                                "be in SRU namespace, but endpoint put it " +
1184                                "into default namespace");
1185                    }
1186                    reader.readEnd("", "echoedSearchRetrieveRequest", true);
1187                }
1188
1189                // searchRetrieveResponse/diagnostics
1190                final List<SRUDiagnostic> diagnostics =
1191                        parseDiagnostics(reader, strictMode);
1192                if (diagnostics != null) {
1193                    handler.onDiagnostics(diagnostics);
1194                }
1195
1196                // explainResponse/extraResponseData
1197                if (reader.readStart(SRU_NS, "extraResponseData", false)) {
1198                    reader.consumeWhitespace();
1199                    proxy.reset(reader);
1200                    try {
1201                        handler.onExtraResponseData(proxy);
1202                    } catch (XMLStreamException e) {
1203                        throw new SRUClientException("handler triggered "
1204                                + "error while parsing 'extraResponseData'", e);
1205                    }
1206                    reader.consumeWhitespace();
1207                    reader.readEnd(SRU_NS, "extraResponseData", true);
1208                }
1209
1210                reader.readEnd(SRU_NS, "searchRetrieveResponse");
1211            }
1212        } catch (XMLStreamException e) {
1213            throw new SRUClientException(e.getMessage(), e);
1214        }
1215    }
1216
1217
1218    private static SRUVersion parseVersion(SRUXMLStreamReader reader)
1219        throws XMLStreamException, SRUClientException {
1220        final String v = reader.readContent(SRU_NS, "version", true);
1221        if (VERSION_1_1.equals(v)) {
1222            return SRUVersion.VERSION_1_1;
1223        } else if (VERSION_1_2.equals(v)) {
1224            return SRUVersion.VERSION_1_2;
1225        } else {
1226            throw new SRUClientException("invalid value '" + v +
1227                    "' for version (valid values are: '" + VERSION_1_1 +
1228                    "' and '" + VERSION_1_2 + "')");
1229        }
1230    }
1231
1232
1233    private static List<SRUDiagnostic> parseDiagnostics(
1234            SRUXMLStreamReader reader, boolean strictMode)
1235            throws XMLStreamException, SRUClientException {
1236        if (reader.readStart(SRU_NS, "diagnostics", false)) {
1237            List<SRUDiagnostic> diagnostics = null;
1238
1239            SRUDiagnostic diagnostic = null;
1240            while ((diagnostic = parseDiagnostic(reader,
1241                    (diagnostics == null), strictMode)) != null) {
1242                if (diagnostics == null) {
1243                    diagnostics = new ArrayList<SRUDiagnostic>();
1244                }
1245                diagnostics.add(diagnostic);
1246            } // while
1247            reader.readEnd(SRU_NS, "diagnostics");
1248            return diagnostics;
1249        } else {
1250            return null;
1251        }
1252    }
1253
1254
1255    private static SRUDiagnostic parseDiagnostic(SRUXMLStreamReader reader,
1256            boolean required, boolean strictMode) throws XMLStreamException,
1257            SRUClientException {
1258        if (reader.readStart(SRU_DIAGNOSIC_NS, "diagnostic", required)) {
1259
1260            // diagnostic/uri
1261            String uri = reader.readContent(SRU_DIAGNOSIC_NS, "uri", true);
1262
1263            String details = null;
1264            String message = null;
1265            if (strictMode) {
1266                // diagnostic/details
1267                details = reader.readContent(SRU_DIAGNOSIC_NS, "details",
1268                        false, true);
1269
1270                // diagnostic/message
1271                message = reader.readContent(SRU_DIAGNOSIC_NS, "message",
1272                        false, true);
1273            } else {
1274                /*
1275                 * common error: diagnostic/details and diagnostic/message may
1276                 * appear in any order
1277                 */
1278                if (reader.peekStart(SRU_DIAGNOSIC_NS, "details")) {
1279                    details = reader.readContent(SRU_DIAGNOSIC_NS, "details",
1280                            false, false);
1281                    message = reader.readContent(SRU_DIAGNOSIC_NS, "message",
1282                            false, false);
1283                } else {
1284                    message = reader.readContent(SRU_DIAGNOSIC_NS, "message",
1285                            false, false);
1286                    details = reader.readContent(SRU_DIAGNOSIC_NS, "details",
1287                            false, false);
1288                    if ((message != null) && (details != null)) {
1289                        logger.error("element <message> and element " +
1290                                "<details> within element <diagnostic> " +
1291                                "appeared in wrong order");
1292                    }
1293                }
1294            }
1295
1296            if ((details != null) && details.isEmpty()) {
1297                details = null;
1298                logger.debug("omitting empty element <details> " +
1299                        "within element <diagnostic>");
1300            }
1301            if ((message != null) && message.isEmpty()) {
1302                message = null;
1303                logger.debug("omitting empty element <message> " +
1304                        "within element <diagnostic>");
1305            }
1306
1307            reader.readEnd(SRU_DIAGNOSIC_NS, "diagnostic");
1308
1309            logger.debug("diagnostic: uri={}, detail={}, message={}",
1310                    uri, details, message);
1311            return new SRUDiagnostic(uri, details, message);
1312        } else {
1313            return null;
1314        }
1315    }
1316
1317
1318    private static SRURecordPacking parseRecordPacking(
1319            SRUXMLStreamReader reader, boolean strictMode)
1320            throws XMLStreamException, SRUClientException {
1321        final String v = reader.readContent(SRU_NS, "recordPacking", true);
1322
1323        if (RECORD_PACKING_XML.equals(v)) {
1324            return SRURecordPacking.XML;
1325        } else if (RECORD_PACKING_STRING.equals(v)) {
1326            return SRURecordPacking.STRING;
1327        } else if (!strictMode && RECORD_PACKING_XML.equalsIgnoreCase(v)) {
1328            logger.error("invalid value '{}' for record packing, should be '{}'",
1329                         v, RECORD_PACKING_XML);
1330            return SRURecordPacking.XML;
1331        } else if (!strictMode && RECORD_PACKING_STRING.equalsIgnoreCase(v)) {
1332            logger.error("invalid value '{}' for record packing, should be '{}'",
1333                         v, RECORD_PACKING_STRING);
1334            return SRURecordPacking.STRING;
1335
1336        } else {
1337            throw new SRUClientException("invalid value '" + v +
1338                    "' for record packing (valid values are: '" +
1339                    RECORD_PACKING_XML + "' and '" + RECORD_PACKING_STRING +
1340                    "')");
1341        }
1342    }
1343
1344
1345    private SRURecordDataParser findParser(String schema) {
1346        SRURecordDataParser parser = parsers.get(schema);
1347        if (parser == null) {
1348            parser = parsers.get(SRUClientConstants.RECORD_DATA_PARSER_SCHEMA_ANY);
1349        }
1350        return parser;
1351    }
1352
1353
1354    private SRUXMLStreamReader createReader(InputStream in, boolean wrap)
1355            throws XMLStreamException {
1356        return new SRUXMLStreamReader(in, wrap);
1357    }
1358
1359} // class SRUSimpleClient
Note: See TracBrowser for help on using the repository browser.