source: SRUClient/tags/SRUClient-2.0.0/src/main/java/eu/clarin/sru/client/SRUSimpleClient.java @ 7174

Last change on this file since 7174 was 7174, checked in by Oliver Schonefeld, 6 years ago
  • tag version 2.0.0
  • Property svn:eol-style set to native
File size: 66.4 KB
Line 
1/**
2 * This software is copyright (c) 2012-2016 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.HttpStatus;
35import org.apache.http.StatusLine;
36import org.apache.http.client.ClientProtocolException;
37import org.apache.http.client.config.CookieSpecs;
38import org.apache.http.client.config.RequestConfig;
39import org.apache.http.client.methods.CloseableHttpResponse;
40import org.apache.http.client.methods.HttpGet;
41import org.apache.http.config.SocketConfig;
42import org.apache.http.impl.NoConnectionReuseStrategy;
43import org.apache.http.impl.client.CloseableHttpClient;
44import org.apache.http.impl.client.HttpClients;
45import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
46import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
48
49
50/**
51 * A simple client to perform SRU operations using callbacks. The application
52 * must provide the appropriate callbacks to receive the results of the
53 * operations.
54 * <p>
55 * This client is reusable but not thread-safe: the application may reuse a
56 * client object, but it may not be concurrently shared between multiple
57 * threads.
58 * </p>
59 * <p>
60 * This class is modeled after Java's SAX-API.
61 * </p>
62 *
63 * @see SRUExplainHandler
64 * @see SRUScanHandler
65 * @see SRUSearchRetrieveHandler
66 * @see SRUDefaultHandlerAdapter
67 */
68public class SRUSimpleClient {
69    private static final String USER_AGENT = "SRU-Client/1.0.0";
70    /** default version the client will use, if not otherwise specified */
71    private static final String SRU_DIAGNOSTIC_RECORD_SCHEMA =
72            "info:srw/schema/1/diagnostics-v1.1";
73    private static final String VERSION_1_1 = "1.1";
74    private static final String VERSION_1_2 = "1.2";
75    private static final String VERSION_2_0 = "2.0";
76    private static final String RECORD_PACKING_PACKED = "packed";
77    private static final String RECORD_PACKING_UNPACKED = "unpacked";
78    private static final String RECORD_ESCAPING_XML = "xml";
79    private static final String RECORD_ESCAPING_STRING = "string";
80    private static final Logger logger =
81            LoggerFactory.getLogger(SRUSimpleClient.class);
82    private final SRUVersion defaultVersion;
83    private final Map<String, SRURecordDataParser> parsers;
84    private final CloseableHttpClient httpClient;
85    private final XmlStreamReaderProxy proxy = new XmlStreamReaderProxy();
86    private final SRUExplainRecordDataParser explainRecordParser =
87            new SRUExplainRecordDataParser();
88
89
90    /**
91     * Constructor.
92     *
93     * @param config
94     *            the configuration to be used for this client.
95     * @throws NullPointerException
96     *             if argument <code>config</code> is <code>null</code>
97     * @throws IllegalArgumentException
98     *             if an error occurred while registering record data parsers
99     * @see SRUClientConfig
100     */
101    public SRUSimpleClient(final SRUClientConfig config) {
102        if (config == null) {
103            throw new NullPointerException("config == null");
104        }
105        this.defaultVersion = config.getDefaultVersion();
106
107        // Initialize parsers lookup table ...
108        final List<SRURecordDataParser> list = config.getRecordDataParsers();
109        if ((list == null) || list.isEmpty()) {
110            throw new IllegalArgumentException(
111                    "no record data parsers registered");
112        }
113        this.parsers = new HashMap<String, SRURecordDataParser>();
114        for (SRURecordDataParser parser : list) {
115            final String recordSchema = parser.getRecordSchema();
116            if (!parsers.containsKey(recordSchema)) {
117                parsers.put(recordSchema, parser);
118            } else {
119                throw new IllegalArgumentException(
120                        "record data parser already registered: " +
121                                recordSchema);
122            }
123        }
124
125        // create HTTP client
126        httpClient = createHttpClient(config.getConnectTimeout(),
127                                      config.getSocketTimeout());
128    }
129
130
131    /**
132     * Perform a <em>explain</em> operation.
133     *
134     * @param request
135     *            an instance of a {@link SRUExplainRequest} object
136     * @param handler
137     *            an instance of {@link SRUExplainHandler} to receive callbacks
138     *            when processing the result of this request
139     * @throws SRUClientException
140     *             if an unrecoverable error occurred
141     * @throws NullPointerException
142     *             if any required argument is <code>null</code>
143     * @see SRUExplainRequest
144     * @see SRUExplainHandler
145     */
146    public void explain(SRUExplainRequest request, SRUExplainHandler handler)
147            throws SRUClientException {
148        if (request == null) {
149            throw new NullPointerException("request == null");
150        }
151        if (handler == null) {
152            throw new NullPointerException("handler == null");
153        }
154        logger.debug("performing explain request");
155
156        final long ts_start = System.nanoTime();
157
158        // create URI and perform request
159        final URI uri = request.makeURI(defaultVersion);
160        CloseableHttpResponse response = executeRequest(uri);
161        InputStream stream             = null;
162        SRUXMLStreamReader reader      = null;
163        try {
164            final HttpEntity entity = response.getEntity();
165            if (entity == null) {
166                throw new SRUClientException("cannot get entity");
167            }
168
169            stream = entity.getContent();
170
171            final long ts_parsing = System.nanoTime();
172            reader = createReader(stream, true);
173            parseExplainResponse(reader, request, handler);
174            final long ts_end = System.nanoTime();
175
176            final long millisTotal =
177                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_start);
178            final long millisNetwork =
179                    TimeUnit.NANOSECONDS.toMillis(ts_parsing - ts_start);
180            final long millisProcessing =
181                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_parsing);
182            logger.debug("{} byte(s) in {} milli(s) " +
183                    "({} milli(s) network / {} milli(s) processing)",
184                    reader.getByteCount(), millisTotal, millisNetwork,
185                    millisProcessing);
186            handler.onRequestStatistics((int) reader.getByteCount(),
187                    millisTotal, millisNetwork, millisProcessing);
188        } catch (IllegalStateException e) {
189            throw new SRUClientException("error reading response", e);
190        } catch (IOException e) {
191            throw new SRUClientException("error reading response", e);
192        } catch (XMLStreamException e) {
193            throw new SRUClientException("error reading response", e);
194        } finally {
195            if (reader != null) {
196                try {
197                    reader.close();
198                } catch (XMLStreamException e) {
199                    /* IGNORE */
200                }
201            }
202            if (stream != null) {
203                try {
204                    stream.close();
205                } catch (IOException e) {
206                    /* IGNORE */
207                }
208            }
209
210            /* make sure to release allocated resources */
211            try {
212                response.close();
213            } catch (IOException e) {
214                /* IGNORE */
215            }
216        }
217    }
218
219
220    /**
221     * Perform a <em>scan</em> operation.
222     *
223     * @param request
224     *            an instance of a {@link SRUScanRequest} object
225     * @param handler
226     *            an instance of {@link SRUScanHandler} to receive callbacks
227     *            when processing the result of this request
228     * @throws SRUClientException
229     *             if an unrecoverable error occurred
230     * @throws NullPointerException
231     *             if any required argument is <code>null</code>
232     * @see SRUScanRequest
233     * @see SRUScanHandler
234     */
235    public void scan(SRUScanRequest request, SRUScanHandler handler)
236            throws SRUClientException {
237        if (request == null) {
238            throw new NullPointerException("request == null");
239        }
240        if (handler == null) {
241            throw new NullPointerException("handler == null");
242        }
243        logger.debug("performing scan request: scanClause = {}",
244                request.getScanClause());
245
246        final long ts_start = System.nanoTime();
247
248        // create URI and perform request
249        final URI uri = request.makeURI(defaultVersion);
250        CloseableHttpResponse response = executeRequest(uri);
251        InputStream stream             = null;
252        SRUXMLStreamReader reader      = null;
253        try {
254            final HttpEntity entity = response.getEntity();
255            if (entity == null) {
256                throw new SRUClientException("cannot get entity");
257            }
258            stream = entity.getContent();
259
260            final long ts_parsing = System.nanoTime();
261            reader = createReader(stream, true);
262            parseScanResponse(reader, request, handler);
263            final long ts_end = System.nanoTime();
264
265            final long millisTotal =
266                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_start);
267            final long millisNetwork =
268                    TimeUnit.NANOSECONDS.toMillis(ts_parsing - ts_start);
269            final long millisProcessing =
270                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_parsing);
271            logger.debug("{} byte(s) in {} milli(s) " +
272                    "({} milli(s) network / {} milli(s) processing)",
273                    reader.getByteCount(), millisTotal, millisNetwork,
274                    millisProcessing);
275            handler.onRequestStatistics((int) reader.getByteCount(),
276                    millisTotal, millisNetwork, millisProcessing);
277        } catch (IllegalStateException e) {
278            throw new SRUClientException("error reading response", e);
279        } catch (IOException e) {
280            throw new SRUClientException("error reading response", e);
281        } catch (XMLStreamException e) {
282            throw new SRUClientException("error reading response", e);
283        } finally {
284            if (reader != null) {
285                try {
286                    reader.close();
287                } catch (XMLStreamException e) {
288                    /* IGNORE */
289                }
290            }
291            if (stream != null) {
292                try {
293                    stream.close();
294                } catch (IOException e) {
295                    /* IGNORE */
296                }
297            }
298
299            /* make sure to release allocated resources */
300            try {
301                response.close();
302            } catch (IOException e) {
303                /* IGNORE */
304            }
305        }
306    }
307
308
309    /**
310     * Perform a <em>searchRetrieve</em> operation.
311     *
312     * @param request
313     *            an instance of a {@link SRUSearchRetrieveRequest} object
314     * @param handler
315     *            an instance of {@link SRUSearchRetrieveHandler} to receive
316     *            callbacks when processing the result of this request
317     * @throws SRUClientException
318     *             if an unrecoverable error occurred
319     * @throws NullPointerException
320     *             if any required argument is <code>null</code>
321     * @see SRUSearchRetrieveRequest
322     * @see SRUSearchRetrieveHandler
323     */
324    public void searchRetrieve(SRUSearchRetrieveRequest request,
325            SRUSearchRetrieveHandler handler) throws SRUClientException {
326        if (request == null) {
327            throw new NullPointerException("request == null");
328        }
329        if (handler == null) {
330            throw new NullPointerException("handler == null");
331        }
332        logger.debug("performing searchRetrieve request: query = {}",
333                request.getQuery());
334
335        final long ts_start = System.nanoTime();
336
337        // create URI and perform request
338        final URI uri = request.makeURI(defaultVersion);
339        CloseableHttpResponse response = executeRequest(uri);
340        InputStream stream             = null;
341        SRUXMLStreamReader reader      = null;
342        try {
343            final HttpEntity entity = response.getEntity();
344            if (entity == null) {
345                throw new SRUClientException("cannot get entity");
346            }
347
348            stream = entity.getContent();
349
350            final long ts_parsing = System.nanoTime();
351            reader = createReader(stream, true);
352            parseSearchRetrieveResponse(reader, request, handler);
353            final long ts_end = System.nanoTime();
354
355            final long millisTotal =
356                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_start);
357            final long millisNetwork =
358                    TimeUnit.NANOSECONDS.toMillis(ts_parsing - ts_start);
359            final long millisProcessing =
360                    TimeUnit.NANOSECONDS.toMillis(ts_end - ts_parsing);
361            logger.debug("{} byte(s) in {} milli(s) " +
362                    "({} milli(s) network / {} milli(s) processing)",
363                    reader.getByteCount(), millisTotal, millisNetwork,
364                    millisProcessing);
365            handler.onRequestStatistics((int) reader.getByteCount(),
366                    millisTotal, millisNetwork, millisProcessing);
367        } catch (IllegalStateException e) {
368            throw new SRUClientException("error reading response", e);
369        } catch (IOException e) {
370            throw new SRUClientException("error reading response", e);
371        } catch (XMLStreamException e) {
372            throw new SRUClientException("error reading response", e);
373        } finally {
374            if (reader != null) {
375                try {
376                    reader.close();
377                } catch (XMLStreamException e) {
378                    /* IGNORE */
379                }
380            }
381            if (stream != null) {
382                try {
383                    stream.close();
384                } catch (IOException e) {
385                    /* IGNORE */
386                }
387            }
388
389            /* make sure to release allocated resources */
390            try {
391                response.close();
392            } catch (IOException e) {
393                /* IGNORE */
394            }
395        }
396    }
397
398
399    private CloseableHttpResponse executeRequest(URI uri)
400            throws SRUClientException {
401        CloseableHttpResponse response = null;
402        boolean forceClose             = true;
403        try {
404            logger.debug("submitting HTTP request: {}", uri.toString());
405            try {
406                HttpGet request = new HttpGet(uri);
407                response        = httpClient.execute(request);
408                StatusLine status = response.getStatusLine();
409                if (status.getStatusCode() != HttpStatus.SC_OK) {
410                    if (status.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
411                        throw new SRUClientException("not found: " + uri);
412                    } else {
413                        throw new SRUClientException("unexpected status: " +
414                                status.getStatusCode());
415                    }
416                }
417                forceClose = false;
418                return response;
419            } catch (ClientProtocolException e) {
420                throw new SRUClientException("client protocol exception", e);
421            } catch (UnknownHostException e) {
422                throw new SRUClientException(
423                        "unknown host: " + uri.getHost(), e);
424            } catch (IOException e) {
425                String msg = null;
426                if ((e.getMessage() != null) && !e.getMessage().isEmpty()) {
427                    msg = e.getMessage();
428                }
429                throw new SRUClientException(msg != null
430                        ? msg
431                        : "input/output error", e);
432            }
433        } catch (SRUClientException e) {
434            /*
435             * if an error occurred, make sure we are freeing up the resources
436             * we've used
437             */
438            if (forceClose && (response != null)) {
439                try {
440                    response.close();
441                } catch (IOException ex) {
442                    /* IGNORE */
443                }
444            }
445            throw e;
446        }
447    }
448
449
450    private void parseExplainResponse(final SRUXMLStreamReader reader,
451            final SRUExplainRequest request, final SRUExplainHandler handler)
452            throws SRUClientException {
453        logger.debug("parsing 'explain' response (mode = {})",
454                (request.isStrictMode() ? "strict" : "non-strict"));
455
456        /* detect response namespaces */
457        final SRUNamespaces ns =
458                detectNamespace(reader, request.getRequestedVersion());
459
460        /*
461         * Eventually, SRUClient should always parse explain record data.
462         * However, for now, make caller explicitly ask for it.
463         */
464        final boolean parse = request.isParseRecordDataEnabled();
465        if (!parse) {
466            logger.debug("parsing of explain record data skipped");
467        }
468        doParseExplainResponse(reader, ns, request, handler, parse);
469    }
470
471
472    private void doParseExplainResponse(SRUXMLStreamReader reader,
473            SRUNamespaces ns, SRUAbstractRequest request,
474            SRUExplainHandler handler, boolean parseRecordData)
475                    throws SRUClientException {
476        try {
477            final boolean strictMode = request.isStrictMode();
478
479            // explainResponse
480            reader.readStart(ns.sruNS(), "explainResponse", true);
481
482            // explainResponse/version
483            final SRUVersion version = parseVersion(reader, ns.sruNS());
484            logger.debug("version = {}, requested = {}", version,
485                    request.getRequestedVersion());
486
487            // explainResponse/record
488            reader.readStart(ns.sruNS(), "record", true);
489            if (parseRecordData) {
490                handler.onStartRecords(-1, null, -1);
491
492                /*
493                 * common error: recordEscaping / recordPacking (SRU 1.2) in
494                 * wrong order
495                 */
496                SRURecordXmlEscaping recordXmlEscaping = null;
497                if (!strictMode && reader.peekStart(ns.sruNS(), "recordPacking")) {
498                    recordXmlEscaping = parseRecordXmlEscaping(reader, ns,
499                            request.getRequestedVersion(), false);
500                    if (recordXmlEscaping != null) {
501                        logger.error("element <recordPacking> must apperear " +
502                                "after element <recordSchema> within " +
503                                "element <record>");
504                    }
505                }
506
507                String schema =
508                        reader.readContent(ns.sruNS(), "recordSchema", true);
509
510                // (SRU 2.0) recordPacking (optional)
511                // XXX: what to do with it?
512                SRURecordPacking recordPacking = null;
513                if (version.isVersion(SRUVersion.VERSION_2_0)) {
514                    recordPacking = parseRecordPacking(reader,
515                            ns.sruNS(), strictMode);
516                }
517
518                // recordXmlEscaping (used to be recordPacking in SRU 1.0/1.2)
519                if (recordXmlEscaping == null) {
520                    recordXmlEscaping = parseRecordXmlEscaping(reader, ns,
521                            request.getRequestedVersion(), strictMode);
522                }
523                logger.debug("schema = {}, escaping = {}, packing = {}",
524                        schema, recordXmlEscaping, recordPacking);
525
526                // explainResponse/record/recordData
527                reader.readStart(ns.sruNS(), "recordData", true);
528                reader.consumeWhitespace();
529
530                SRURecordData recordData = null;
531                SRUXMLStreamReader recordReader = null;
532
533                if (recordXmlEscaping == SRURecordXmlEscaping.STRING) {
534                    /*
535                     * read content into temporary buffer and then use a new XML
536                     * reader to parse record data
537                     */
538                    final String data = reader.readString(true);
539                    InputStream in = new ByteArrayInputStream(data.getBytes());
540                    // FIXME: namespace context?
541                    recordReader = createReader(in, false);
542                } else {
543                    recordReader = reader;
544                }
545
546                try {
547                    proxy.reset(recordReader);
548                    /*
549                     * Try to parse explain record data with explain record
550                     * parser. It will throw an exception, if it cannot handle
551                     * the data.
552                     */
553                    recordData = explainRecordParser.parse(proxy,
554                            version, strictMode, schema);
555                } catch (XMLStreamException e) {
556                    throw new SRUClientException(
557                            "error parsing explain record", e);
558                } finally {
559                    /*
560                     * make sure, we're deallocate the record reader in case of
561                     * string record packing
562                     */
563                    if (recordXmlEscaping == SRURecordXmlEscaping.STRING) {
564                        recordReader.closeCompletly();
565                    }
566                }
567                if (recordData == null) {
568                    throw new SRUClientException("no record data could " +
569                            "be extracted from the response");
570                }
571                reader.consumeWhitespace();
572                reader.readEnd(ns.sruNS(), "recordData", true);
573
574                if (version.isVersion(SRUVersion.VERSION_1_2,
575                        SRUVersion.VERSION_2_0)) {
576                    reader.readContent(ns.sruNS(), "recordIdentifier", false);
577                }
578                reader.readContent(ns.sruNS(), "recordPosition", false, -1);
579
580                // notify handler
581                handler.onRecord(null, -1, recordData);
582
583                if (reader.readStart(ns.sruNS(), "extraRecordData", false)) {
584                    reader.consumeWhitespace();
585                    proxy.reset(reader);
586                    try {
587                        logger.debug("parsing extra response data");
588                        handler.onExtraRecordData(null, -1, proxy);
589                    } catch (XMLStreamException e) {
590                        throw new SRUClientException("handler "
591                                + "triggered error while parsing "
592                                + "'extraRecordData'", e);
593                    }
594                    reader.consumeWhitespace();
595                    reader.readEnd(ns.sruNS(), "extraRecordData", true);
596                }
597
598                handler.onFinishRecords(-1);
599
600                reader.readEnd(ns.sruNS(), "record");
601            } else {
602                /*
603                 * do not really parse record and skip everything
604                 * until <record> end tag
605                 */
606                reader.readEnd(ns.sruNS(), "record", true);
607            }
608
609            // explainResponse/echoedExplainRequest
610            if (reader.readStart(ns.sruNS(), "echoedExplainRequest", false)) {
611                reader.readEnd(ns.sruNS(), "echoedExplainRequest", true);
612            }
613
614            /*
615             * common error: echoedExplainRequest in default namespace
616             */
617            if (reader.readStart("", "echoedExplainRequest", false)) {
618                logger.error("Element 'echoedExplainRequest' must be in SRU " +
619                        "namespace, but endpoint put it into default namespace");
620                if (strictMode) {
621                    throw new SRUClientException("Element " +
622                            "'echoedExplainRequest' must be in SRU namespace,"+
623                            " but endpoint put it into default namespace");
624                }
625                reader.readEnd("", "echoedExplainRequest", true);
626            }
627
628            // explainResponse/diagnostics
629            final List<SRUDiagnostic> diagnostics =
630                    parseDiagnostics(reader, ns, ns.scanNS(), strictMode);
631            if (diagnostics != null) {
632                handler.onDiagnostics(diagnostics);
633            }
634
635            // explainResponse/extraResponseData
636            if (reader.readStart(ns.sruNS(), "extraResponseData", false)) {
637                reader.consumeWhitespace();
638                proxy.reset(reader);
639                try {
640                    logger.debug("parsing extra response data");
641                    handler.onExtraResponseData(proxy);
642                } catch (XMLStreamException e) {
643                    throw new SRUClientException("handler triggered "
644                            + "error while parsing 'extraResponseData'", e);
645                }
646                reader.consumeWhitespace();
647                reader.readEnd(ns.sruNS(), "extraResponseData", true);
648            }
649
650            reader.readEnd(ns.sruNS(), "explainResponse");
651        } catch (XMLStreamException e) {
652            throw new SRUClientException(e.getMessage(), e);
653        }
654    }
655
656
657    private void parseScanResponse(final SRUXMLStreamReader reader,
658            final SRUScanRequest request, final SRUScanHandler handler)
659            throws SRUClientException {
660        try {
661            /* detect response namespaces */
662            final SRUNamespaces ns =
663                    detectNamespace(reader, request.getRequestedVersion());
664
665            /*
666             * if the endpoint cannot determine the operation, it should create
667             * a explain response.
668             */
669            if (reader.peekStart(ns.sruNS(), "explainResponse")) {
670                doParseExplainResponse(reader, ns, request, new SRUExplainHandler() {
671                    @Override
672                    public void onRequestStatistics(int bytes, long millisTotal,
673                            long millisNetwork, long millisParsing) {
674                    }
675
676
677                    @Override
678                    public void onExtraResponseData(XMLStreamReader reader)
679                            throws XMLStreamException, SRUClientException {
680                    }
681
682
683                    @Override
684                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
685                            throws SRUClientException {
686                        handler.onDiagnostics(diagnostics);
687                    }
688
689
690                    @Override
691                    public void onStartRecords(int numberOfRecords,
692                            String resultSetId, int resultSetIdleTime)
693                            throws SRUClientException {
694                    }
695
696
697                    @Override
698                    public void onFinishRecords(int nextRecordPosition)
699                            throws SRUClientException {
700                    }
701
702
703                    @Override
704                    public void onRecord(String identifier, int position,
705                            SRURecordData data) throws SRUClientException {
706                    }
707
708
709                    @Override
710                    public void onExtraRecordData(String identifier,
711                            int position, XMLStreamReader reader)
712                            throws XMLStreamException, SRUClientException {
713                    }
714                }, false);
715            } else {
716                final boolean strictMode = request.isStrictMode();
717
718                logger.debug("parsing 'scan' response (mode = {})",
719                        (strictMode ? "strict" : "non-strict"));
720
721                // scanResponse
722                reader.readStart(ns.scanNS(), "scanResponse", true);
723
724                // scanResponse/version
725                final SRUVersion version = parseVersion(reader, ns.scanNS());
726                logger.debug("version = {}, requested = {}", version,
727                        request.getRequestedVersion());
728
729                // scanResponse/terms
730                if (reader.readStart(ns.scanNS(), "terms", false)) {
731                    boolean first = true;
732                    while (reader.readStart(ns.scanNS(), "term", first)) {
733                        if (first) {
734                            first = false;
735                            handler.onStartTerms();
736                        }
737
738                        // scanResponse/terms/value
739                        String value =
740                                reader.readContent(ns.scanNS(), "value", true);
741
742                        // scanResponse/terms/numberOfRecords
743                        int numberOfRecords = reader.readContent(ns.scanNS(),
744                                "numberOfRecords", false, -1);
745
746                        // scanResponse/terms/displayTerm
747                        String displayTerm = reader.readContent(ns.scanNS(),
748                                "displayTerm", false);
749
750                        // scanResponse/terms/whereInList
751                        String s = reader.readContent(ns.scanNS(),
752                                "whereInList", false);
753                        SRUWhereInList whereInList = null;
754                        if (s != null) {
755                            if ("first".equals(s)) {
756                                whereInList = SRUWhereInList.FIRST;
757                            } else if ("last".equals(s)) {
758                                whereInList = SRUWhereInList.LAST;
759                            } else if ("only".equals(s)) {
760                                whereInList = SRUWhereInList.ONLY;
761                            } else if ("inner".equals(s)) {
762                                whereInList = SRUWhereInList.INNER;
763                            } else {
764                                throw new SRUClientException(
765                                        "invalid value for 'whereInList': " + s);
766                            }
767                        }
768                        logger.debug("value = {}, numberOfRecords = {}, " +
769                                "displayTerm = {}, whereInList = {}", value,
770                                numberOfRecords, displayTerm, whereInList);
771                        handler.onTerm(value, numberOfRecords, displayTerm,
772                                whereInList);
773
774                        // scanResponse/terms/extraTermData
775                        if (reader.readStart(ns.scanNS(), "extraTermData", first)) {
776                            reader.consumeWhitespace();
777                            proxy.reset(reader);
778                            try {
779                                handler.onExtraTermData(value, proxy);
780                            } catch (XMLStreamException e) {
781                                throw new SRUClientException("handler "
782                                        + "triggered error while parsing "
783                                        + "'extraTermData'", e);
784                            }
785                            reader.consumeWhitespace();
786                            reader.readEnd(ns.scanNS(), "extraTermData", true);
787                        }
788                        reader.readEnd(ns.scanNS(), "term", true);
789
790                    } // while
791                    reader.readEnd(ns.scanNS(), "terms");
792                    handler.onFinishTerms();
793                }
794
795                // scanResponse/echoedScanRequest
796                if (reader.readStart(ns.scanNS(), "echoedScanRequest", false)) {
797                    reader.readEnd(ns.scanNS(), "echoedScanRequest", true);
798                }
799
800                /*
801                 * common error: echoedScanRequest in default namespace
802                 */
803                if (reader.readStart("", "echoedScanRequest", false)) {
804                    logger.error("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
805                    if (strictMode) {
806                        throw new SRUClientException("Element 'echoedScanRequest' must be in SRU namespace, but endpoint put it into default namespace");
807                    }
808                    reader.readEnd("", "echoedScanRequest", true);
809                }
810
811                // scanResponse/diagnostics
812                final List<SRUDiagnostic> diagnostics =
813                        parseDiagnostics(reader, ns, ns.scanNS(), strictMode);
814                if (diagnostics != null) {
815                    handler.onDiagnostics(diagnostics);
816                }
817
818                // scanResponse/extraResponseData
819                if (reader.readStart(ns.scanNS(), "extraResponseData", false)) {
820                    reader.consumeWhitespace();
821                    proxy.reset(reader);
822                    try {
823                        logger.debug("parsing extra response data");
824                        handler.onExtraResponseData(proxy);
825                    } catch (XMLStreamException e) {
826                        throw new SRUClientException("handler triggered "
827                                + "error while parsing 'extraResponseData'", e);
828                    }
829                    reader.consumeWhitespace();
830                    reader.readEnd(ns.scanNS(), "extraResponseData", true);
831                }
832
833                reader.readEnd(ns.scanNS(), "scanResponse");
834            }
835        } catch (XMLStreamException e) {
836            throw new SRUClientException(e.getMessage(), e);
837        }
838    }
839
840
841    private void parseSearchRetrieveResponse(final SRUXMLStreamReader reader,
842            final SRUSearchRetrieveRequest request,
843            final SRUSearchRetrieveHandler handler) throws SRUClientException {
844        try {
845            /* detect response namespaces */
846            final SRUNamespaces ns =
847                    detectNamespace(reader, request.getRequestedVersion());
848
849            /*
850             * if the endpoint cannot determine the operation, it should create
851             * a explain response.
852             */
853            if (reader.peekStart(ns.sruNS(), "explainResponse")) {
854                doParseExplainResponse(reader, ns, request, new SRUExplainHandler() {
855                    @Override
856                    public void onRequestStatistics(int bytes, long millisTotal,
857                            long millisNetwork, long millisParsing) {
858                    }
859
860
861                    @Override
862                    public void onExtraResponseData(XMLStreamReader reader)
863                            throws XMLStreamException, SRUClientException {
864                    }
865
866
867                    @Override
868                    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
869                            throws SRUClientException {
870                        handler.onDiagnostics(diagnostics);
871                    }
872
873
874                    @Override
875                    public void onStartRecords(int numberOfRecords,
876                            String resultSetId, int resultSetIdleTime)
877                            throws SRUClientException {
878                    }
879
880
881                    @Override
882                    public void onFinishRecords(int nextRecordPosition)
883                            throws SRUClientException {
884                    }
885
886
887                    @Override
888                    public void onRecord(String identifier, int position,
889                            SRURecordData data) throws SRUClientException {
890                    }
891
892
893                    @Override
894                    public void onExtraRecordData(String identifier,
895                            int position, XMLStreamReader reader)
896                            throws XMLStreamException, SRUClientException {
897                    }
898                }, false);
899            } else {
900                final boolean strictMode = request.isStrictMode();
901
902                logger.debug("parsing 'searchRetrieve' response (mode = {})",
903                        (strictMode ? "strict" : "non-strict"));
904
905                // searchRetrieveResponse
906                reader.readStart(ns.sruNS(), "searchRetrieveResponse", true);
907
908                // searchRetrieveResponse/version
909                final SRUVersion version = parseVersion(reader, ns.sruNS());
910                logger.debug("version = {}, requested = {}", version,
911                        request.getRequestedVersion());
912
913                // searchRetrieveResponse/numberOfRecords
914                int numberOfRecords = reader.readContent(ns.sruNS(),
915                        "numberOfRecords", true, -1);
916
917                // searchRetrieveResponse/resultSetId
918                String resultSetId = reader.readContent(ns.sruNS(),
919                        "resultSetId", false);
920
921                // searchRetrieveResponse/resultSetIdleTime
922                int resultSetIdleTime = reader.readContent(ns.sruNS(),
923                        "resultSetIdleTime", false, -1);
924
925                logger.debug("numberOfRecords = {}, resultSetId = {}, " +
926                        "resultSetIdleTime = {}", numberOfRecords,
927                        resultSetId, resultSetIdleTime);
928
929                // searchRetrieveResponse/results
930                if (numberOfRecords > 0) {
931                    /*
932                     * some endpoints set numberOfRecords but do not serialize
933                     * any records, e.g if requested record schema is not
934                     * supported
935                     */
936                    int recordCount = 0;
937                    if (reader.readStart(ns.sruNS(), "records", false)) {
938                        // searchRetrieveResponse/records/record
939                        boolean first = true;
940                        while (reader.readStart(ns.sruNS(), "record", first)) {
941                            if (first) {
942                                first = false;
943                                handler.onStartRecords(numberOfRecords,
944                                        resultSetId, resultSetIdleTime);
945                            }
946
947                            /*
948                             * common error: recordEscaping / recordPacking
949                             * (SRU 1.2) in wrong order
950                             */
951                            SRURecordXmlEscaping recordXmlEscaping = null;
952                            if (!strictMode &&
953                                    reader.peekStart(ns.sruNS(), "recordPacking")) {
954                                recordXmlEscaping =
955                                        parseRecordXmlEscaping(reader, ns,
956                                                version, false);
957                                if (recordXmlEscaping != null) {
958                                    logger.error("element <recordPacking> " +
959                                            "must appear after element " +
960                                            "<recordSchema> within " +
961                                            "element <record>");
962                                }
963                            }
964
965                            String schema = reader.readContent(ns.sruNS(),
966                                    "recordSchema", true);
967
968                            // (SRU 2.0) recordPacking (optional)
969                            // XXX: what to do with it?
970                            SRURecordPacking recordPacking = null;
971                            if (version.isVersion(SRUVersion.VERSION_2_0)) {
972                                recordPacking = parseRecordPacking(reader,
973                                        ns.sruNS(), strictMode);
974                            }
975
976                            if (recordXmlEscaping == null) {
977                                recordXmlEscaping =
978                                        parseRecordXmlEscaping(reader, ns,
979                                                version, strictMode);
980                            }
981
982                            logger.debug("schema = {}, escpaing = {}, " +
983                                    "packing = {}, requested escaping = {}, " +
984                                    "requested packing = {}",
985                                    schema, recordXmlEscaping, recordPacking,
986                                    request.getRecordXmlEscaping(),
987                                    request.getRecordPacking());
988
989                            if ((request.getRecordXmlEscaping() != null) &&
990                                    (recordXmlEscaping != request.getRecordXmlEscaping())) {
991                                final SRURecordXmlEscaping p =
992                                        request.getRecordXmlEscaping();
993                                logger.error("requested '{}' record XML escaping, " +
994                                        "but server responded with '{}' " +
995                                        "record XML escaping",
996                                        p.getStringValue(),
997                                        recordXmlEscaping.getStringValue());
998                                if (strictMode) {
999                                    throw new SRUClientException("requested '" +
1000                                            p.getStringValue() +
1001                                            "' record packing, but server " +
1002                                            "responded with '" +
1003                                            recordXmlEscaping.getStringValue() +
1004                                            "' record packing");
1005                                }
1006                            }
1007
1008                            // searchRetrieveResponse/record/recordData
1009                            reader.readStart(ns.sruNS(), "recordData", true);
1010                            reader.consumeWhitespace();
1011
1012                            SRURecordData recordData = null;
1013                            SRUDiagnostic surrogate = null;
1014                            SRUXMLStreamReader recordReader = null;
1015
1016                            if (recordXmlEscaping == SRURecordXmlEscaping.STRING) {
1017                                /*
1018                                 * read content into temporary buffer and then
1019                                 * use a new XML reader to parse record data
1020                                 */
1021                                final String data = reader.readString(true);
1022                                InputStream in = new ByteArrayInputStream(
1023                                        data.getBytes());
1024                                // FIXME: namespace context?
1025                                recordReader = createReader(in, false);
1026                            } else {
1027                                recordReader = reader;
1028                            }
1029
1030                            if (SRU_DIAGNOSTIC_RECORD_SCHEMA.equals(schema)) {
1031                                surrogate = parseDiagnostic(recordReader, ns,
1032                                        true, strictMode);
1033                            } else {
1034                                SRURecordDataParser parser = findParser(schema);
1035                                if (parser != null) {
1036                                    try {
1037                                        proxy.reset(recordReader);
1038                                        recordData = parser.parse(proxy);
1039                                    } catch (XMLStreamException e) {
1040                                        throw new SRUClientException(
1041                                                "error parsing record", e);
1042                                    } finally {
1043                                        /*
1044                                         * make sure, we deallocate the record
1045                                         * reader in case of string record
1046                                         * packing
1047                                         */
1048                                        if (recordXmlEscaping == SRURecordXmlEscaping.STRING) {
1049                                            recordReader.closeCompletly();
1050                                        }
1051                                    }
1052                                    if (recordData == null) {
1053                                        logger.debug("record parser did not parse "
1054                                                + "record correctly and returned "
1055                                                + "null; injecting client side "
1056                                                + "surrogate diagnostic");
1057                                        surrogate = new SRUDiagnostic(
1058                                                SRUClientDiagnostics.DIAG_RECORD_PARSER_NULL,
1059                                                null,
1060                                                "Record parser for schema '" +
1061                                                        schema +
1062                                                        "' did not " +
1063                                                        "parse record correctly " +
1064                                                        "and errornously " +
1065                                                        "returned null.");
1066                                    }
1067                                } else {
1068                                    /*
1069                                     * no record parser found, inject a
1070                                     * surrogate diagnostic
1071                                     */
1072                                    logger.debug(
1073                                            "no record data parser found "
1074                                                    + "for schema '{}'; injecting client "
1075                                                    + "side surrogate diagnostic",
1076                                            schema);
1077                                    surrogate = new SRUDiagnostic(
1078                                            SRUClientDiagnostics.DIAG_NO_RECORD_PARSER,
1079                                            schema,
1080                                            "No record data parser for schema '" +
1081                                                    schema + "' found.");
1082                                }
1083                            }
1084
1085                            reader.consumeWhitespace();
1086                            reader.readEnd(ns.sruNS(), "recordData", true);
1087
1088                            String identifier = null;
1089                            if (version.isVersion(SRUVersion.VERSION_1_2,
1090                                    SRUVersion.VERSION_2_0)) {
1091                                identifier = reader.readContent(ns.sruNS(),
1092                                        "recordIdentifier", false);
1093                            }
1094
1095                            int position = reader.readContent(ns.sruNS(),
1096                                    "recordPosition", false, -1);
1097
1098                            logger.debug("recordIdentifier = {}, " +
1099                                    "recordPosition = {}",
1100                                    identifier, position);
1101
1102                            // notify handler
1103                            if (surrogate != null) {
1104                                handler.onSurrogateRecord(identifier,
1105                                        position, surrogate);
1106                            } else {
1107                                if (recordData != null) {
1108                                    handler.onRecord(identifier,
1109                                            position, recordData);
1110                                }
1111                            }
1112
1113                            if (reader.readStart(ns.sruNS(),
1114                                    "extraRecordData", false)) {
1115                                reader.consumeWhitespace();
1116                                proxy.reset(reader);
1117                                try {
1118                                    handler.onExtraRecordData(identifier,
1119                                            position, proxy);
1120                                } catch (XMLStreamException e) {
1121                                    throw new SRUClientException("handler " +
1122                                            "triggered error while parsing " +
1123                                            "'extraRecordData'", e);
1124                                }
1125                                reader.consumeWhitespace();
1126                                reader.readEnd(ns.sruNS(), "extraRecordData", true);
1127                            }
1128
1129                            reader.readEnd(ns.sruNS(), "record");
1130                            recordCount++;
1131                        } // while
1132                        reader.readEnd(ns.sruNS(), "records");
1133                    }
1134                    if (recordCount == 0) {
1135                        logger.error("endpoint declared {} results, but response contained no <record> elements (behavior may violate SRU specification)", numberOfRecords);
1136                    } else if ((request.getMaximumRecords() != -1) && (recordCount > request.getMaximumRecords())) {
1137                        logger.error("endpoint did not honour 'maximumRecords' request parameter and responded with {} records instead of a maximum of {}", recordCount, request.getMaximumRecords());
1138                    }
1139                } else {
1140                    /*
1141                     * provide a better error format, if endpoints responds with
1142                     * an empty <records> element
1143                     */
1144                    if (reader.readStart(ns.sruNS(), "records", false)) {
1145                        int bad = 0;
1146                        while (reader.readStart(ns.sruNS(), "record", false)) {
1147                            bad++;
1148                            reader.readEnd(ns.sruNS(), "record", true);
1149                        }
1150                        reader.readEnd(ns.sruNS(), "records", true);
1151                        if (bad == 0) {
1152                            logger.error("endpoint declared 0 results, but " +
1153                                    "response contained an empty 'records' " +
1154                                    "element");
1155                            if (strictMode) {
1156                                throw new SRUClientException(
1157                                        "endpoint declared 0 results, but " +
1158                                        "response contained an empty " +
1159                                        "'records' element (behavior " +
1160                                        "violates SRU specification)");
1161                            }
1162                        } else {
1163                            logger.error("endpoint declared 0 results, but " +
1164                                    "response contained " + bad +
1165                                    " record(s)");
1166                            if (strictMode) {
1167                                throw new SRUClientException(
1168                                            "endpoint declared 0 results, " +
1169                                            "but response containted " + bad +
1170                                            " records (behavior may violate " +
1171                                            "SRU specification)");
1172                            }
1173                        }
1174                    }
1175                }
1176
1177                int nextRecordPosition = reader.readContent(ns.sruNS(),
1178                        "nextRecordPosition", false, -1);
1179                logger.debug("nextRecordPosition = {}", nextRecordPosition);
1180                handler.onFinishRecords(nextRecordPosition);
1181
1182                // searchRetrieveResponse/echoedSearchRetrieveResponse
1183                if (reader.readStart(ns.sruNS(),
1184                        "echoedSearchRetrieveRequest", false)) {
1185                    reader.readEnd(ns.sruNS(), "echoedSearchRetrieveRequest", true);
1186                }
1187
1188                /*
1189                 * common error: echoedSearchRetrieveRequest in
1190                 * default namespace
1191                 */
1192                if (reader.readStart("", "echoedSearchRetrieveRequest", false)) {
1193                    logger.error("Element 'echoedSearchRetrieveRequest' " +
1194                            "must be in SRU namespace, but endpoint put it " +
1195                            "into default namespace");
1196                    if (strictMode) {
1197                        throw new SRUClientException(
1198                                "Element 'echoedSearchRetrieveRequest' must " +
1199                                "be in SRU namespace, but endpoint put it " +
1200                                "into default namespace");
1201                    }
1202                    reader.readEnd("", "echoedSearchRetrieveRequest", true);
1203                }
1204
1205                // searchRetrieveResponse/diagnostics
1206                final List<SRUDiagnostic> diagnostics =
1207                        parseDiagnostics(reader, ns, ns.sruNS(), strictMode);
1208                if (diagnostics != null) {
1209                    handler.onDiagnostics(diagnostics);
1210                }
1211
1212                // explainResponse/extraResponseData
1213                if (reader.readStart(ns.sruNS(), "extraResponseData", false)) {
1214                    reader.consumeWhitespace();
1215                    proxy.reset(reader);
1216                    try {
1217                        handler.onExtraResponseData(proxy);
1218                    } catch (XMLStreamException e) {
1219                        throw new SRUClientException("handler triggered "
1220                                + "error while parsing 'extraResponseData'", e);
1221                    }
1222                    reader.consumeWhitespace();
1223                    reader.readEnd(ns.sruNS(), "extraResponseData", true);
1224                }
1225
1226                if (version.isVersion(SRUVersion.VERSION_2_0)) {
1227                    // SRU (2.0) arbitrary stuff
1228                    // SRU (2.0) resultSetTTL (replaces resultSetIdleTime)
1229                    // SRU (2.0) resultCountPrecision
1230                    if (reader.readStart(ns.sruNS(), "resultCountPrecision", false)) {
1231                        reader.readEnd(ns.sruNS(), "resultCountPrecision", true);
1232                    }
1233                    // SRU (2.0) facetedResults
1234                    // SRU (2.0) searchResultAnalysis
1235                }
1236                reader.readEnd(ns.sruNS(), "searchRetrieveResponse");
1237            }
1238        } catch (XMLStreamException e) {
1239            throw new SRUClientException(e.getMessage(), e);
1240        }
1241    }
1242
1243
1244    private static SRUVersion parseVersion(SRUXMLStreamReader reader,
1245            String envelopNs) throws XMLStreamException, SRUClientException {
1246        final String v = reader.readContent(envelopNs, "version", true);
1247        if (VERSION_1_1.equals(v)) {
1248            return SRUVersion.VERSION_1_1;
1249        } else if (VERSION_1_2.equals(v)) {
1250            return SRUVersion.VERSION_1_2;
1251        } else if (VERSION_2_0.equals(v)) {
1252            return SRUVersion.VERSION_2_0;
1253        } else {
1254            throw new SRUClientException("invalid value '" + v +
1255                    "' for version (valid values are: '" + VERSION_1_1 +
1256                    "' and '" + VERSION_1_2 + "')");
1257        }
1258    }
1259
1260
1261    private static List<SRUDiagnostic> parseDiagnostics(
1262            SRUXMLStreamReader reader, SRUNamespaces ns, String responseNs, boolean strictMode)
1263            throws XMLStreamException, SRUClientException {
1264        if (reader.readStart(responseNs, "diagnostics", false)) {
1265            List<SRUDiagnostic> diagnostics = null;
1266
1267            SRUDiagnostic diagnostic = null;
1268            while ((diagnostic = parseDiagnostic(reader, ns,
1269                    (diagnostics == null), strictMode)) != null) {
1270                if (diagnostics == null) {
1271                    diagnostics = new ArrayList<SRUDiagnostic>();
1272                }
1273                diagnostics.add(diagnostic);
1274            } // while
1275            reader.readEnd(responseNs, "diagnostics");
1276            return diagnostics;
1277        } else {
1278            return null;
1279        }
1280    }
1281
1282
1283    private static SRUDiagnostic parseDiagnostic(SRUXMLStreamReader reader,
1284            SRUNamespaces ns, boolean required, boolean strictMode) throws XMLStreamException,
1285            SRUClientException {
1286        if (reader.readStart(ns.diagnosticNS(), "diagnostic", required)) {
1287
1288            // diagnostic/uri
1289            String uri = reader.readContent(ns.diagnosticNS(), "uri", true);
1290
1291            String details = null;
1292            String message = null;
1293            if (strictMode) {
1294                // diagnostic/details
1295                details = reader.readContent(ns.diagnosticNS(), "details",
1296                        false, true);
1297
1298                // diagnostic/message
1299                message = reader.readContent(ns.diagnosticNS(), "message",
1300                        false, true);
1301            } else {
1302                /*
1303                 * common error: diagnostic/details and diagnostic/message may
1304                 * appear in any order
1305                 */
1306                if (reader.peekStart(ns.diagnosticNS(), "details")) {
1307                    details = reader.readContent(ns.diagnosticNS(), "details",
1308                            false, false);
1309                    message = reader.readContent(ns.diagnosticNS(), "message",
1310                            false, false);
1311                } else {
1312                    message = reader.readContent(ns.diagnosticNS(), "message",
1313                            false, false);
1314                    details = reader.readContent(ns.diagnosticNS(), "details",
1315                            false, false);
1316                    if ((message != null) && (details != null)) {
1317                        logger.error("element <message> and element " +
1318                                "<details> within element <diagnostic> " +
1319                                "appeared in wrong order");
1320                    }
1321                }
1322            }
1323
1324            if ((details != null) && details.isEmpty()) {
1325                details = null;
1326                logger.debug("omitting empty element <details> " +
1327                        "within element <diagnostic>");
1328            }
1329            if ((message != null) && message.isEmpty()) {
1330                message = null;
1331                logger.debug("omitting empty element <message> " +
1332                        "within element <diagnostic>");
1333            }
1334
1335            reader.readEnd(ns.diagnosticNS(), "diagnostic");
1336
1337            logger.debug("diagnostic: uri={}, detail={}, message={}",
1338                    uri, details, message);
1339            return new SRUDiagnostic(uri, details, message);
1340        } else {
1341            return null;
1342        }
1343    }
1344
1345
1346    private static SRURecordPacking parseRecordPacking(
1347            SRUXMLStreamReader reader, String envelopNs, boolean strictMode)
1348                    throws XMLStreamException, SRUClientException {
1349        final String s = reader.readContent(envelopNs, "recordPacking", false);
1350        if (s != null) {
1351            if (RECORD_PACKING_PACKED.equals(s)) {
1352                return SRURecordPacking.PACKED;
1353            } else if (RECORD_PACKING_UNPACKED.equals(s)) {
1354                return SRURecordPacking.UNPACKED;
1355            } else if (!strictMode && RECORD_PACKING_PACKED.equalsIgnoreCase(s)) {
1356                logger.error("invalid value '{}' for '<recordPacking>', should be '{}'",
1357                             s, RECORD_PACKING_PACKED);
1358                return SRURecordPacking.PACKED;
1359            } else if (!strictMode && RECORD_PACKING_UNPACKED.equalsIgnoreCase(s)) {
1360                logger.error("invalid value '{}' for '<recordPacking>', should be '{}'",
1361                             s, RECORD_PACKING_UNPACKED);
1362                return SRURecordPacking.UNPACKED;
1363            } else {
1364                throw new SRUClientException("invalid value '" + s +
1365                        "' for '<recordPacking>' (valid values are: '" +
1366                        RECORD_PACKING_PACKED + "' and '" + RECORD_PACKING_UNPACKED +
1367                        "')");
1368            }
1369        }
1370        return null;
1371    }
1372
1373
1374    private static SRURecordXmlEscaping parseRecordXmlEscaping(
1375            SRUXMLStreamReader reader, SRUNamespaces ns, SRUVersion version, boolean strictMode)
1376            throws XMLStreamException, SRUClientException {
1377
1378        final String name = version.isVersion(SRUVersion.VERSION_2_0)
1379                          ? "recordXMLEscaping" : "recordPacking";
1380        final String s = reader.readContent(ns.sruNS(), name, true);
1381
1382        if (RECORD_ESCAPING_XML.equals(s)) {
1383            return SRURecordXmlEscaping.XML;
1384        } else if (RECORD_ESCAPING_STRING.equals(s)) {
1385            return SRURecordXmlEscaping.STRING;
1386        } else if (!strictMode && RECORD_ESCAPING_XML.equalsIgnoreCase(s)) {
1387            logger.error("invalid value '{}' for '<{}>', should be '{}'",
1388                         s, name, RECORD_ESCAPING_XML);
1389            return SRURecordXmlEscaping.XML;
1390        } else if (!strictMode && RECORD_ESCAPING_STRING.equalsIgnoreCase(s)) {
1391            logger.error("invalid value '{}' for '<{}>', should be '{}'",
1392                         s, name, RECORD_ESCAPING_STRING);
1393            return SRURecordXmlEscaping.STRING;
1394        } else {
1395            throw new SRUClientException("invalid value '" + s +
1396                    "' for '<" + name + ">' (valid values are: '" +
1397                    RECORD_ESCAPING_XML + "' and '" + RECORD_ESCAPING_STRING +
1398                    "')");
1399        }
1400    }
1401
1402
1403    private SRURecordDataParser findParser(String schema) {
1404        SRURecordDataParser parser = parsers.get(schema);
1405        if (parser == null) {
1406            parser = parsers.get(SRUClientConstants.RECORD_DATA_PARSER_SCHEMA_ANY);
1407        }
1408        return parser;
1409    }
1410
1411
1412    private static SRUXMLStreamReader createReader(InputStream in, boolean wrap)
1413            throws XMLStreamException {
1414        return new SRUXMLStreamReader(in, wrap);
1415    }
1416
1417
1418    private static CloseableHttpClient createHttpClient(int connectTimeout,
1419            int socketTimeout) {
1420        final PoolingHttpClientConnectionManager manager =
1421                new PoolingHttpClientConnectionManager();
1422        manager.setDefaultMaxPerRoute(8);
1423        manager.setMaxTotal(128);
1424
1425        final SocketConfig socketConfig = SocketConfig.custom()
1426                .setSoReuseAddress(true)
1427                .setSoLinger(0)
1428                .build();
1429
1430        final RequestConfig requestConfig = RequestConfig.custom()
1431                .setAuthenticationEnabled(false)
1432                .setRedirectsEnabled(true)
1433                .setMaxRedirects(4)
1434                .setCircularRedirectsAllowed(false)
1435                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
1436                .setConnectTimeout(connectTimeout)
1437                .setSocketTimeout(socketTimeout)
1438                .setConnectionRequestTimeout(0) /* infinite */
1439                .setStaleConnectionCheckEnabled(false)
1440                .build();
1441
1442        return HttpClients.custom()
1443                .setUserAgent(USER_AGENT)
1444                .setConnectionManager(manager)
1445                .setDefaultSocketConfig(socketConfig)
1446                .setDefaultRequestConfig(requestConfig)
1447                .setConnectionReuseStrategy(new NoConnectionReuseStrategy())
1448                .build();
1449    }
1450
1451
1452    private static SRUNamespaces detectNamespace(XMLStreamReader reader,
1453            SRUVersion requestedVersion)
1454                    throws SRUClientException {
1455        try {
1456            // skip to first start tag
1457            reader.nextTag();
1458
1459            final String namespaceURI = reader.getNamespaceURI();
1460            logger.debug("found namespace URI '{}', requested version = {}",
1461                    namespaceURI, requestedVersion);
1462
1463            SRUNamespaces namespace;
1464            if (NAMESPACES_LEGACY_LOC.matchNamespace(namespaceURI)) {
1465                namespace = NAMESPACES_LEGACY_LOC;
1466            } else if (NAMESPACES_OASIS.matchNamespace(namespaceURI)) {
1467                namespace = NAMESPACES_OASIS;
1468            } else {
1469                throw new SRUClientException(
1470                        "invalid namespace '" + reader.getNamespaceURI() + "'");
1471            }
1472
1473            if (requestedVersion != null) {
1474                if (!namespace.compatibleWithVersion(requestedVersion)) {
1475                    throw new SRUClientException("Server did not honour " +
1476                            "requested SRU version and responded with " +
1477                            "different version (requested version was " +
1478                            requestedVersion + ")");
1479                }
1480            }
1481            return namespace;
1482        } catch (XMLStreamException e) {
1483            throw new SRUClientException("error detecting namespace", e);
1484        }
1485    }
1486
1487
1488    private interface SRUNamespaces {
1489        public boolean compatibleWithVersion(SRUVersion version);
1490
1491        public String sruNS();
1492
1493        public String scanNS();
1494
1495        public String diagnosticNS();
1496
1497        public boolean matchNamespace(String namespaceURI);
1498
1499    } // interface SRUNamespace
1500
1501
1502    private static final SRUNamespaces NAMESPACES_LEGACY_LOC = new SRUNamespaces() {
1503        private static final String SRU_NS =
1504                "http://www.loc.gov/zing/srw/";
1505        private static final String SRU_DIAGNOSIC_NS =
1506                "http://www.loc.gov/zing/srw/diagnostic/";
1507
1508
1509        @Override
1510        public boolean compatibleWithVersion(SRUVersion version) {
1511            return SRUVersion.VERSION_1_1.equals(version) ||
1512                    SRUVersion.VERSION_1_2.equals(version);
1513        }
1514
1515
1516        @Override
1517        public String sruNS() {
1518            return SRU_NS;
1519        }
1520
1521
1522        @Override
1523        public String scanNS() {
1524            return SRU_NS;
1525        }
1526
1527
1528        @Override
1529        public String diagnosticNS() {
1530            return SRU_DIAGNOSIC_NS;
1531        }
1532
1533
1534        @Override
1535        public boolean matchNamespace(String namespaceURI) {
1536            return SRU_NS.equals(namespaceURI);
1537        }
1538    };
1539
1540
1541    private static final SRUNamespaces NAMESPACES_OASIS = new SRUNamespaces() {
1542        private static final String SRU_NS =
1543                "http://docs.oasis-open.org/ns/search-ws/sruResponse";
1544        private static final String SRU_SCAN_NS =
1545                "http://docs.oasis-open.org/ns/search-ws/scan";
1546        private static final String SRU_DIAGNOSIC_NS =
1547                "http://docs.oasis-open.org/ns/search-ws/diagnostic";
1548
1549
1550        @Override
1551        public boolean compatibleWithVersion(SRUVersion version) {
1552            return SRUVersion.VERSION_2_0.equals(version);
1553        }
1554
1555
1556        @Override
1557        public String sruNS() {
1558            return SRU_NS;
1559        }
1560
1561
1562        @Override
1563        public String scanNS() {
1564            return SRU_SCAN_NS;
1565
1566        }
1567
1568
1569        @Override
1570        public String diagnosticNS() {
1571            return SRU_DIAGNOSIC_NS;
1572        }
1573
1574
1575        @Override
1576        public boolean matchNamespace(String namespaceURI) {
1577            return SRU_NS.equals(namespaceURI) || SRU_SCAN_NS.equals(namespaceURI);
1578        }
1579    };
1580
1581} // class SRUSimpleClient
Note: See TracBrowser for help on using the repository browser.