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

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