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

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