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

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