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

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