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

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