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

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