source: SRUClient/tags/SRUClient-0.9.5/src/main/java/eu/clarin/sru/client/SRUClient.java @ 6079

Last change on this file since 6079 was 6079, checked in by Oliver Schonefeld, 9 years ago
  • tag version 0.9.5
  • Property svn:eol-style set to native
File size: 19.0 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.util.ArrayDeque;
20import java.util.ArrayList;
21import java.util.Deque;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.concurrent.TimeUnit;
25
26import javax.xml.XMLConstants;
27import javax.xml.namespace.QName;
28import javax.xml.parsers.DocumentBuilder;
29import javax.xml.parsers.DocumentBuilderFactory;
30import javax.xml.parsers.ParserConfigurationException;
31import javax.xml.stream.XMLStreamConstants;
32import javax.xml.stream.XMLStreamException;
33import javax.xml.stream.XMLStreamReader;
34
35import org.slf4j.Logger;
36import org.slf4j.LoggerFactory;
37import org.w3c.dom.Attr;
38import org.w3c.dom.DOMException;
39import org.w3c.dom.Document;
40import org.w3c.dom.DocumentFragment;
41import org.w3c.dom.Element;
42import org.w3c.dom.Node;
43
44
45/**
46 * A client to perform SRU operations. The response of a SRU request is wrapped
47 * in a SRU response.
48 * <p>
49 * This client is reusable but not thread-safe: the application may reuse a
50 * client object, but it may not be concurrently shared between multiple
51 * threads.
52 * </p>
53 */
54public class SRUClient {
55    private static final Logger logger = LoggerFactory.getLogger(SRUClient.class);
56    private final SRUSimpleClient client;
57    private final Handler handler;
58    /* common */
59    private List<SRUDiagnostic> diagnostics;
60    private List<SRUExtraResponseData> extraResponseData;
61    /* scan */
62    private List<SRUTerm> terms;
63    /* searchRetrieve */
64    private int numberOfRecords;
65    private String resultSetId;
66    private int resultSetIdleTime;
67    private int nextRecordPosition;
68    /* explain/searchRetrieve */
69    private List<SRURecord> records;
70    /* statistics */
71    private int totalBytesTransferred;
72    private long timeTotal;
73    private long timeQueued;
74    private long timeNetwork;
75    private long timeParsing;
76    /* other fields */
77    private final DocumentBuilder documentBuilder;
78    private final Deque<Node> stack = new ArrayDeque<Node>();
79
80
81    /**
82     * Constructor.
83     *
84     * @param config
85     *            the configuration to be used for this client.
86     * @throws NullPointerException
87     *             if argument <code>config</code> is <node>null</code>
88     * @throws IllegalArgumentException
89     *             if an error occurred while registering record data parsers
90     * @see SRUClientConfig
91     */
92    public SRUClient(final SRUClientConfig config) {
93        this(config, DocumentBuilderFactory.newInstance());
94    }
95
96
97    /**
98     * Constructor.
99     *
100     * <p>
101     * For internal use only.
102     * </p>
103     *
104     * @param defaultVersion
105     *            the default version to use for SRU requests; may be overridden
106     *            by individual requests
107     * @param parsers
108     *            a <code>Map</code> to store record schema to record data
109     *            parser mappings
110     * @param documentBuilderFactory
111     *            the Document Builder factory to be used to create Document
112     *            Builders
113     */
114    SRUClient(final SRUClientConfig config,
115            final DocumentBuilderFactory documentBuilderFactory) {
116        if (config == null) {
117            throw new NullPointerException("config == null");
118        }
119        if (documentBuilderFactory == null) {
120            throw new NullPointerException("documentBuilderFactory == null");
121        }
122        this.client = new SRUSimpleClient(config);
123        this.handler = new Handler(config);
124        try {
125            synchronized (documentBuilderFactory) {
126                documentBuilderFactory.setNamespaceAware(true);
127                documentBuilderFactory.setCoalescing(true);
128                this.documentBuilder =
129                        documentBuilderFactory.newDocumentBuilder();
130            } // documentBuilderFactory (documentBuilderFactory)
131        } catch (ParserConfigurationException e) {
132            throw new Error("error initialzing document builder factory", e);
133        }
134        reset();
135    }
136
137
138    /**
139     * Perform a <em>explain</em> operation.
140     *
141     * @param request
142     *            an instance of a {@link SRUExplainRequest} object
143     * @return a {@link SRUExplainResponse} object
144     * @throws SRUClientException
145     *             if an unrecoverable error occurred
146     * @throws NullPointerException
147     *             if any required argument is <code>null</code>
148     */
149    public SRUExplainResponse explain(SRUExplainRequest request)
150            throws SRUClientException {
151        if (request == null) {
152            throw new NullPointerException("request == null");
153        }
154        try {
155            client.explain(request, handler);
156            SRURecord record = null;
157            if ((records != null) && !records.isEmpty()) {
158                record = records.get(0);
159            }
160            return new SRUExplainResponse(request,
161                    diagnostics,
162                    extraResponseData,
163                    totalBytesTransferred,
164                    timeTotal,
165                    timeQueued,
166                    timeNetwork,
167                    timeParsing,
168                    record);
169        } finally {
170            reset();
171        }
172    }
173
174
175    /**
176     * Perform a <em>scan</em> operation.
177     *
178     * @param request
179     *            an instance of a {@link SRUScanRequest} object
180     * @return a {@link SRUScanResponse} object
181     * @throws SRUClientException
182     *             if an unrecoverable error occurred
183     * @throws NullPointerException
184     *             if any required argument is <code>null</code>
185     */
186    public SRUScanResponse scan(SRUScanRequest request)
187            throws SRUClientException {
188        if (request == null) {
189            throw new NullPointerException("request == null");
190        }
191        try {
192            client.scan(request, handler);
193            return new SRUScanResponse(request,
194                    diagnostics,
195                    extraResponseData,
196                    totalBytesTransferred,
197                    timeTotal,
198                    timeQueued,
199                    timeNetwork,
200                    timeParsing,
201                    terms);
202        } finally {
203            reset();
204        }
205
206    }
207
208
209    /**
210     * Perform a <em>searchRetrieve</em> operation.
211     *
212     * @param request
213     *            an instance of a {@link SRUSearchRetrieveRequest} object
214     * @return a {@link SRUSearchRetrieveRequest} object
215     * @throws SRUClientException
216     *             if an unrecoverable error occurred
217     * @throws NullPointerException
218     *             if any required argument is <code>null</code>
219     */
220    public SRUSearchRetrieveResponse searchRetrieve(
221            SRUSearchRetrieveRequest request) throws SRUClientException {
222        if (request == null) {
223            throw new NullPointerException("request == null");
224        }
225        try {
226            client.searchRetrieve(request, handler);
227            return new SRUSearchRetrieveResponse(request,
228                    diagnostics,
229                    extraResponseData,
230                    totalBytesTransferred,
231                    timeTotal,
232                    timeQueued,
233                    timeNetwork,
234                    timeParsing,
235                    numberOfRecords,
236                    resultSetId,
237                    resultSetIdleTime,
238                    records,
239                    nextRecordPosition);
240        } finally {
241            reset();
242        }
243    }
244
245
246    void setTimeQueued(long timeQueued) {
247        this.timeQueued = TimeUnit.NANOSECONDS.toMillis(timeQueued);
248    }
249
250
251    private void addTerm(SRUTerm term) {
252        if (terms == null) {
253            terms = new LinkedList<SRUTerm>();
254        }
255        terms.add(term);
256    }
257
258
259    private void addRecord(SRURecord record) {
260        if (records == null) {
261            records = new LinkedList<SRURecord>();
262        }
263        records.add(record);
264    }
265
266
267    private void reset() {
268        /* common */
269        diagnostics           = null;
270        extraResponseData     = null;
271        /* scan */
272        terms                 = null;
273        /* searchRetrieve */
274        numberOfRecords       = -1;
275        resultSetId           = null;
276        resultSetIdleTime     = -1;
277        nextRecordPosition    = -1;
278        /* explain/searchRetrieve */
279        records               = null;
280        /* statistics */
281        totalBytesTransferred = -1;
282        timeQueued            = -1;
283        timeTotal             = -1;
284        timeNetwork           = -1;
285        timeParsing           = -1;
286    }
287
288
289
290    private class Handler extends SRUDefaultHandlerAdapter {
291        private final List<SRUExtraResponseDataParser> parsers;
292
293
294        private Handler(SRUClientConfig config) {
295            this.parsers = config.getExtraResponseDataParsers();
296        }
297
298
299        @Override
300        public void onDiagnostics(List<SRUDiagnostic> diagnostics)
301                throws SRUClientException {
302            SRUClient.this.diagnostics = diagnostics;
303        }
304
305
306        @Override
307        public void onExtraResponseData(XMLStreamReader reader)
308                throws XMLStreamException, SRUClientException {
309            if (reader.getEventType() == XMLStreamConstants.START_DOCUMENT) {
310                reader.next();
311            }
312
313            while (reader.hasNext()) {
314                SRUExtraResponseData data = null;
315
316                XmlStreamReaderUtils.consumeWhitespace(reader);
317                switch (reader.getEventType()) {
318                case XMLStreamConstants.START_ELEMENT:
319                    final QName root = reader.getName();
320                    logger.debug("@start: {}", root);
321
322                    /*
323                     * The content model of "extraResponseData" is a sequence
324                     * of elements. Parse each child element into a separate
325                     * entity, i.e. if a parse is available for handling the
326                     * element use it, otherwise parse into a document fragment.
327                     */
328                    if ((parsers != null) && !parsers.isEmpty()) {
329                        for (SRUExtraResponseDataParser parser: parsers) {
330                            if (parser.supports(root)) {
331                                logger.debug("parsing extra response data with parser '{}'",
332                                        parser.getClass().getName());
333                                data = parser.parse(reader);
334                                break;
335                            }
336                        }
337                    }
338                    if (data == null) {
339                        logger.debug("parsing of extra response data (generic)");
340                        data = parseExtraResponseAsDocumentFragment(
341                                root, documentBuilder, stack, reader);
342                    }
343                    break;
344                case XMLStreamConstants.END_DOCUMENT:
345                    break;
346                default:
347                  throw new SRUClientException("expected a start element at " +
348                          "this location (event code = " +
349                          reader.getEventType() + ")");
350                }
351
352                if (data != null) {
353                    if (SRUClient.this.extraResponseData == null) {
354                        SRUClient.this.extraResponseData =
355                                new ArrayList<SRUExtraResponseData>();
356                    }
357                    SRUClient.this.extraResponseData.add(data);
358                }
359            } // while
360
361        }
362
363
364        @Override
365        public void onTerm(String value, int numberOfRecords,
366                String displayTerm, SRUWhereInList whereInList)
367                throws SRUClientException {
368            SRUClient.this.addTerm(new SRUTerm(value, numberOfRecords,
369                    displayTerm, whereInList));
370        }
371
372
373        @Override
374        public void onExtraTermData(String value, XMLStreamReader reader)
375                throws XMLStreamException, SRUClientException {
376            final List<SRUTerm> terms = SRUClient.this.terms;
377            if ((terms != null) && !terms.isEmpty()) {
378                SRUTerm term = terms.get(terms.size() - 1);
379                term.setExtraTermData(copyStaxToDocumentFragment(
380                        documentBuilder, stack, reader));
381            } else {
382                /*
383                 * should never happen ...
384                 */
385                throw new SRUClientException(
386                        "internal error; 'terms' is null or empty");
387            }
388        }
389
390
391        @Override
392        public void onStartRecords(int numberOfRecords, String resultSetId,
393                int resultSetIdleTime) throws SRUClientException {
394            SRUClient.this.resultSetId = resultSetId;
395            SRUClient.this.resultSetIdleTime = resultSetIdleTime;
396        }
397
398
399        @Override
400        public void onFinishRecords(int nextRecordPosition)
401                throws SRUClientException {
402            SRUClient.this.nextRecordPosition = nextRecordPosition;
403        }
404
405
406        @Override
407        public void onRecord(String identifier, int position,
408                SRURecordData data) throws SRUClientException {
409            SRUClient.this.addRecord(
410                    new SRURecord(data, identifier, position));
411        }
412
413
414        @Override
415        public void onSurrogateRecord(String identifier, int position,
416                SRUDiagnostic data) throws SRUClientException {
417            SRUClient.this.addRecord(new SRURecord(
418                    new SRUSurrogateRecordData(data), identifier, position));
419        }
420
421
422        @Override
423        public void onExtraRecordData(String identifier, int position,
424                XMLStreamReader reader) throws XMLStreamException,
425                SRUClientException {
426            final List<SRURecord> records = SRUClient.this.records;
427            if ((records != null) && !records.isEmpty()) {
428                final SRURecord record = records.get(records.size() - 1);
429                record.setExtraRecordData(copyStaxToDocumentFragment(
430                        documentBuilder, stack, reader));
431            } else {
432                /*
433                 * should never happen ...
434                 */
435                throw new SRUClientException(
436                        "internal error; 'records' is null or empty");
437            }
438        }
439
440
441        @Override
442        public void onRequestStatistics(int totalBytesTransferred,
443                long millisTotal, long millisNetwork, long millisProcessing) {
444            SRUClient.this.totalBytesTransferred = totalBytesTransferred;
445            if (SRUClient.this.timeQueued > 0) {
446                SRUClient.this.timeTotal = timeQueued  + millisTotal;
447            } else {
448                SRUClient.this.timeTotal = millisTotal;
449            }
450            SRUClient.this.timeNetwork = millisNetwork;
451            SRUClient.this.timeParsing = millisProcessing;
452        }
453    } // inner class Handler
454
455
456    private static SRUExtraResponseData parseExtraResponseAsDocumentFragment(
457            QName name, DocumentBuilder builder, Deque<Node> stack, XMLStreamReader reader)
458            throws XMLStreamException {
459        return new SRUGenericExtraResponseData(name,
460                copyStaxToDocumentFragment(builder, stack, reader));
461    }
462
463
464    private static DocumentFragment copyStaxToDocumentFragment(
465            DocumentBuilder builder, Deque<Node> stack, XMLStreamReader reader)
466            throws XMLStreamException {
467        try {
468            final Document doc = builder.newDocument();
469            stack.push(doc.createDocumentFragment());
470
471            boolean stop = false;
472            while (!stop && reader.hasNext()) {
473                final Node parent = stack.peek();
474                switch (reader.getEventType()) {
475                case XMLStreamConstants.START_ELEMENT:
476                    stack.push(createElementNode(parent, doc, reader));
477                    break;
478                case XMLStreamConstants.END_ELEMENT:
479                    stack.pop();
480                    if (stack.size() == 1) {
481                        stop = true;
482                    }
483                    break;
484                case XMLStreamConstants.CHARACTERS:
485                    parent.appendChild(doc.createTextNode(reader.getText()));
486                    break;
487                case XMLStreamConstants.COMMENT:
488                    parent.appendChild(doc.createComment(reader.getText()));
489                    break;
490                case XMLStreamConstants.CDATA:
491                    parent.appendChild(doc.createCDATASection(reader.getText()));
492                    break;
493                default:
494                    break;
495                }
496                reader.next();
497            } // while
498            if (stack.size() != 1) {
499                throw new XMLStreamException(
500                        "internal error; stack should hold only one element");
501            }
502            return (DocumentFragment) stack.pop();
503        } catch (DOMException e) {
504            throw new XMLStreamException(
505                    "error creating document fragment", e);
506        }
507    }
508
509
510    private static Element createElementNode(Node parent, Document doc,
511            XMLStreamReader reader) throws XMLStreamException, DOMException {
512        Element element = doc.createElementNS(reader.getNamespaceURI(),
513                reader.getLocalName());
514
515        if ((reader.getPrefix() != null) && !reader.getPrefix().isEmpty()) {
516            element.setPrefix(reader.getPrefix());
517        }
518
519        parent.appendChild(element);
520
521        // add namespace declarations
522        for (int i = 0; i < reader.getNamespaceCount(); i++) {
523            final String uri    = reader.getNamespaceURI(i);
524            final String prefix = reader.getNamespacePrefix(i);
525
526            if ((prefix != null) && !prefix.isEmpty()) {
527                element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
528                        XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
529                        uri);
530            } else {
531                if (uri != null) {
532                    element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
533                            XMLConstants.XMLNS_ATTRIBUTE,
534                            uri);
535                }
536            }
537        }
538
539        // add other attributes
540        for (int i = 0; i < reader.getAttributeCount(); i++) {
541            String name   = reader.getAttributeLocalName(i);
542            String prefix = reader.getAttributePrefix(i);
543            if (prefix != null && prefix.length() > 0) {
544                name = prefix + ":" + name;
545            }
546
547            Attr attr = doc.createAttributeNS(
548                    reader.getAttributeNamespace(i), name);
549            attr.setValue(reader.getAttributeValue(i));
550            element.setAttributeNode(attr);
551        }
552        return element;
553    }
554
555} // class SRUClient
Note: See TracBrowser for help on using the repository browser.