source: SRUServer/trunk/src/main/java/eu/clarin/sru/server/SRUServer.java @ 2734

Last change on this file since 2734 was 2734, checked in by oschonef, 11 years ago
  • fix some typos
  • Property svn:eol-style set to native
File size: 43.4 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.server;
18
19import java.io.FilterOutputStream;
20import java.io.IOException;
21import java.io.OutputStream;
22import java.util.Arrays;
23import java.util.List;
24import java.util.NoSuchElementException;
25
26import javax.servlet.http.HttpServletRequest;
27import javax.servlet.http.HttpServletResponse;
28import javax.xml.stream.XMLOutputFactory;
29import javax.xml.stream.XMLStreamException;
30import javax.xml.stream.XMLStreamWriter;
31
32import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34import org.z3950.zing.cql.CQLNode;
35
36import eu.clarin.sru.server.SRUServerConfig.DatabaseInfo;
37import eu.clarin.sru.server.SRUServerConfig.IndexInfo;
38import eu.clarin.sru.server.SRUServerConfig.LocalizedString;
39import eu.clarin.sru.server.SRUServerConfig.SchemaInfo;
40import eu.clarin.sru.server.utils.SRUServerServlet;
41
42
43/**
44 * SRU/CQL protocol implementation for the server-side (SRU/S). This class
45 * implements SRU/CQL version 1.1 and and 1.2.
46 *
47 * @see SRUServerConfig
48 * @see SRUSearchEngine
49 * @see SRUServerServlet
50 * @see <a href="http://www.loc.gov/standards/sru/">SRU/CQL protocol 1.2</a>
51 */
52public final class SRUServer {
53    private static final String SRU_NS =
54            "http://www.loc.gov/zing/srw/";
55    private static final String SRU_PREFIX = "sru";
56    private static final String SRU_DIAGNOSIC_NS =
57            "http://www.loc.gov/zing/srw/diagnostic/";
58    private static final String SRU_DIAGNOSTIC_PREFIX = "diag";
59    private static final String SRU_DIAGNOSTIC_RECORD_SCHEMA =
60            "info:srw/schema/1/diagnostics-v1.1";
61    private static final String SRU_EXPLAIN_NS =
62            "http://explain.z3950.org/dtd/2.0/";
63    private static final String SRU_EXPLAIN_PREFIX = "zr";
64    private static final String SRU_XCQL_NS =
65            "http://www.loc.gov/zing/cql/xcql/";
66    static final String RESPONSE_ENCODING = "utf-8";
67    private static final String RESPONSE_CONTENT_TYPE = "application/xml";
68    private static final Logger logger =
69            LoggerFactory.getLogger(SRUServer.class);
70    private final SRUServerConfig config;
71    private final SRUSearchEngine searchEngine;
72    private final XMLOutputFactory writerFactory;
73
74
75    /**
76     * Constructor.
77     *
78     * @param config
79     *            a SRUEndpointConfig object
80     * @param searchEngine
81     *            an object implementing the SRUSearchEngine interface
82     * @throws NullPointerException
83     *             if config or searchEngine is <code>null</code>
84     * @throws SRUException
85     *             if an error occurred
86     */
87    public SRUServer(SRUServerConfig config, SRUSearchEngine searchEngine)
88            throws SRUException {
89        if (config == null) {
90            throw new NullPointerException("config == null");
91        }
92        this.config = config;
93        if (searchEngine == null) {
94            throw new NullPointerException("searchEngine == null");
95        }
96        this.searchEngine = searchEngine;
97        this.writerFactory = XMLOutputFactory.newInstance();
98    }
99
100
101    /**
102     * Handle a SRU request.
103     *
104     * @param request
105     *            a HttpServletRequest request
106     * @param response
107     *            a HttpServletResponse request
108     */
109    public void handleRequest(HttpServletRequest request,
110            HttpServletResponse response) {
111        final SRURequestImpl req = new SRURequestImpl(config, request);
112        try {
113            // set response properties
114            response.setContentType(RESPONSE_CONTENT_TYPE);
115            response.setCharacterEncoding(RESPONSE_ENCODING);
116            response.setStatus(HttpServletResponse.SC_OK);
117            // make sure we can reset the stream later in case of error ...
118            response.setBufferSize(config.getResponseBufferSize());
119            try {
120                if (req.checkParameters()) {
121                    switch (req.getOperation()) {
122                    case EXPLAIN:
123                        explain(req, response);
124                        break;
125                    case SCAN:
126                        scan(req, response);
127                        break;
128                    case SEARCH_RETRIEVE:
129                        search(req, response);
130                        break;
131                    }
132                } else {
133                    // (some) parameters are malformed, send error
134                    SRUXMLStreamWriter out =
135                        createXMLStreamWriter(response.getOutputStream(),
136                                SRURecordPacking.XML, false,
137                                req.getIndentResponse());
138                    writeFatalError(out, req, req.getDiagnostics());
139                }
140            } catch (XMLStreamException e) {
141                logger.error("An error occurred while serializing response", e);
142                throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
143                        "An error occurred while serializing response.", e);
144            } catch (IOException e) {
145                /*
146                 * Well, can't really do anything useful here ...
147                 */
148                logger.error("An unexpected exception occurred", e);
149            }
150        } catch (SRUException e) {
151            if (!response.isCommitted()) {
152                if (logger.isInfoEnabled()) {
153                    final String message = e.getDiagnostic().getMessage();
154                    if (message != null) {
155                        logger.info("Sending fatal diagnostic '{}{}' with " +
156                                "message '{}'",
157                                new Object[] {
158                                        SRUConstants.SRU_DIAGNOSTIC_URI_PREFIX,
159                                        e.getDiagnostic().getCode(),
160                                        message
161                                });
162                    } else {
163                        logger.info("Sending fatal diagnostic '{}{}'",
164                                SRUConstants.SRU_DIAGNOSTIC_URI_PREFIX,
165                                e.getDiagnostic().getCode());
166                    }
167                    logger.debug("Fatal diagnostic was caused by " +
168                            "this exception", e);
169                }
170                response.resetBuffer();
171                try {
172                    List<SRUDiagnostic> diagnostics = req.getDiagnostics();
173                    if (diagnostics != null) {
174                        diagnostics.add(e.getDiagnostic());
175                    } else {
176                        diagnostics = Arrays.asList(e.getDiagnostic());
177                    }
178                    SRUXMLStreamWriter out =
179                            createXMLStreamWriter(response.getOutputStream(),
180                                    SRURecordPacking.XML, false,
181                                    req.getIndentResponse());
182                    writeFatalError(out, req, diagnostics);
183                } catch (Exception ex) {
184                    logger.error("An exception occurred while in error state",
185                            ex);
186                }
187            } else {
188                /*
189                 * The Servlet already flushed the output buffer, so cannot
190                 * degrade gracefully anymore and, unfortunately, will produce
191                 * ill-formed XML output.
192                 * Increase the response buffer size, if you want to avoid
193                 * this (at the cost of memory).
194                 */
195                logger.error("A fatal error occurred, but the response was "
196                        + "already committed. Unable to recover gracefully.", e);
197            }
198        }
199    }
200
201
202    private void explain(SRURequestImpl request, HttpServletResponse response)
203            throws IOException, XMLStreamException, SRUException {
204        logger.info("explain");
205
206        // commence explain ...
207        final SRUExplainResult result =
208                searchEngine.explain(config, request, request);
209
210        try {
211            // send results
212            SRUXMLStreamWriter out =
213                    createXMLStreamWriter(response.getOutputStream(),
214                                          request.getRecordPacking(),
215                                          true,
216                                          request.getIndentResponse());
217
218            beginResponse(out, request);
219
220            // write the explain record
221            writeExplainRecord(out, request);
222
223            if (config.getEchoRequests()) {
224                writeEchoedExplainRequest(out, request);
225            }
226
227            // diagnostics
228            writeDiagnosticList(out, request.getDiagnostics());
229
230            // extraResponseData
231            if (result != null) {
232                if (result.hasExtraResponseData()) {
233                    out.writeStartElement(SRU_NS, "extraResponseData");
234                    result.writeExtraResponseData(out);
235                    out.writeEndElement(); // "extraResponseData" element
236                }
237            }
238
239            endResponse(out);
240        } finally {
241            if (result != null) {
242                result.close();
243            }
244        }
245    }
246
247
248    private void scan(SRURequestImpl request, HttpServletResponse response)
249            throws IOException, XMLStreamException, SRUException {
250        logger.info("scan: scanClause = \"{}\"",
251                new Object[] { request.getRawScanClause() });
252
253        // commence scan
254        final SRUScanResultSet result =
255                searchEngine.scan(config, request, request);
256        if (result == null) {
257            throw new SRUException(SRUConstants.SRU_UNSUPPORTED_OPERATION,
258                    "The 'scan' operation is not supported by this endpoint.");
259        }
260
261        try {
262            // send results
263            SRUXMLStreamWriter out =
264                    createXMLStreamWriter(response.getOutputStream(),
265                                          request.getRecordPacking(),
266                                          true,
267                                          request.getIndentResponse());
268
269            beginResponse(out, request);
270
271            try {
272                /*
273                 * a scan result without a list of terms is a valid response;
274                 * make sure, to produce the correct output and omit in that case
275                 * the <terms> ...
276                 */
277                boolean wroteTerms = false;
278                while (result.nextTerm()) {
279                    if (!wroteTerms) {
280                        out.writeStartElement(SRU_NS, "terms");
281                        wroteTerms = true;
282                    }
283                    out.writeStartElement(SRU_NS, "term");
284
285                    out.writeStartElement(SRU_NS, "value");
286                    out.writeCharacters(result.getValue());
287                    out.writeEndElement(); // "value" element
288
289                    if (result.getNumberOfRecords() > -1) {
290                        out.writeStartElement(SRU_NS, "numberOfRecords");
291                        out.writeCharacters(
292                                Integer.toString(result.getNumberOfRecords()));
293                        out.writeEndElement(); // "numberOfRecords" element
294                    }
295
296                    if (result.getDisplayTerm() != null) {
297                        out.writeStartElement(SRU_NS, "displayTerm");
298                        out.writeCharacters(result.getDisplayTerm());
299                        out.writeEndElement(); // "displayTerm" element
300                    }
301
302                    if (result.getWhereInList() != null) {
303                        out.writeStartElement(SRU_NS, "whereInList");
304                        switch (result.getWhereInList()) {
305                        case FIRST:
306                            out.writeCharacters("first");
307                            break;
308                        case LAST:
309                            out.writeCharacters("last");
310                            break;
311                        case ONLY:
312                            out.writeCharacters("only");
313                            break;
314                        case INNER:
315                            out.writeCharacters("inner");
316                            break;
317                        } // switch
318                        out.writeEndElement(); // "whereInList" element
319                    }
320
321                    if (result.hasExtraTermData()) {
322                        out.writeStartElement(SRU_NS, "extraTermData");
323                        result.writeExtraTermData(out);
324                        out.writeEndElement(); // "extraTermData" element
325                    }
326
327                    out.writeEndElement(); // "term" element
328                } // while
329                if (wroteTerms) {
330                    out.writeEndElement(); // "terms" element
331                }
332            } catch (NoSuchElementException e) {
333                throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
334                        "An internal error occurred while "
335                                + "serializing scan results.");
336            }
337
338            // echoedScanRequest
339            if (config.getEchoRequests()) {
340                writeEchoedScanRequest(out, request, request.getScanClause());
341            }
342
343            // diagnostics
344            writeDiagnosticList(out, request.getDiagnostics());
345
346            // extraResponseData
347            if (result.hasExtraResponseData()) {
348                out.writeStartElement(SRU_NS, "extraResponseData");
349                result.writeExtraResponseData(out);
350                out.writeEndElement(); // "extraResponseData" element
351            }
352
353            endResponse(out);
354        } finally {
355            if (result != null) {
356                result.close();
357            }
358        }
359    }
360
361
362    private void search(SRURequestImpl request, HttpServletResponse response)
363            throws IOException, XMLStreamException, SRUException {
364        logger.info("searchRetrieve: query = \"{}\", startRecord = {}, " +
365                "maximumRecords = {}, recordSchema = {}, resultSetTTL = {}",
366                new Object[] { request.getRawQuery(), request.getStartRecord(),
367                        request.getMaximumRecords(),
368                        request.getRecordSchemaIdentifier(),
369                        request.getResultSetTTL() });
370
371        // commence search ...
372        final SRUSearchResultSet result =
373                searchEngine.search(config, request, request);
374        if (result == null) {
375            throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
376                    "SRUSearchEngine implementation returned invalid result (null).");
377        }
378
379
380        // check, of startRecord position is greater than total record set
381        if ((result.getTotalRecordCount() >= 0) &&
382            (request.getStartRecord() > 1) &&
383            (request.getStartRecord() > result.getTotalRecordCount())) {
384            throw new SRUException(
385                    SRUConstants.SRU_FIRST_RECORD_POSITION_OUT_OF_RANGE);
386        }
387
388        try {
389            // send results
390            SRUXMLStreamWriter out =
391                    createXMLStreamWriter(response.getOutputStream(),
392                                          request.getRecordPacking(),
393                                          true,
394                                          request.getIndentResponse());
395
396            beginResponse(out, request);
397
398            // numberOfRecords
399            out.writeStartElement(SRU_NS, "numberOfRecords");
400            out.writeCharacters(
401                    Integer.toString(result.getTotalRecordCount()));
402            out.writeEndElement(); // "numberOfRecords" element
403
404            // resultSetId
405            if (result.getResultSetId() != null) {
406                out.writeStartElement(SRU_NS, "resultSetId");
407                out.writeCharacters(result.getResultSetId());
408                out.writeEndElement(); // "resultSetId" element
409            }
410
411            // resultSetIdleTime
412            if (result.getResultSetIdleTime() > 0) {
413                out.writeStartElement(SRU_NS, "resultSetIdleTime");
414                out.writeCharacters(Integer.toString(result
415                        .getResultSetIdleTime()));
416                out.writeEndElement(); // "resultSetIdleTime" element
417            }
418
419            int position = (request.getStartRecord() > 0)
420                    ? request.getStartRecord() : 1;
421            if (result.getRecordCount() > 0) {
422                final int maxPositionOffset =
423                        (request.getMaximumRecords() != -1)
424                        ? (position + request.getMaximumRecords() - 1)
425                        : -1;
426                try {
427                    out.writeStartElement(SRU_NS, "records");
428                    while (result.nextRecord()) {
429                        /*
430                         * Sanity check: do not return more then the maximum
431                         * requested records. If the search engine
432                         * implementation does not honor limit truncate the
433                         * result set.
434                         */
435                        if ((maxPositionOffset != -1) &&
436                                (position > maxPositionOffset)) {
437                            logger.error("SRUSearchEngine implementation did " +
438                                    "not honor limit for the amount of " +
439                                    "requsted records. Result set truncated!");
440                            break;
441                        }
442
443                        out.writeStartElement(SRU_NS, "record");
444
445                        /*
446                         * We need to output either the record or a surrogate
447                         * diagnostic. In case of the latter, we need to output
448                         * the appropriate record schema ...
449                         */
450                        final SRUDiagnostic diagnostic =
451                                result.getSurrogateDiagnostic();
452
453                        out.writeStartElement(SRU_NS, "recordSchema");
454                        if (diagnostic == null) {
455                            out.writeCharacters(
456                                    result.getRecordSchemaIdentifier());
457                        } else {
458                            out.writeCharacters(SRU_DIAGNOSTIC_RECORD_SCHEMA);
459                        }
460                        out.writeEndElement(); // "recordSchema" element
461
462                        // recordPacking
463                        writeRecordPacking(out, request.getRecordPacking());
464
465                        /*
466                         * Output either record data or surrogate diagnostic ...
467                         */
468                        out.writeStartElement(SRU_NS, "recordData");
469                        out.startRecord();
470                        if (diagnostic == null) {
471                            result.writeRecord(out);
472                        } else {
473                            // write a surrogate diagnostic
474                            writeDiagnostic(out, diagnostic, true);
475                        }
476                        out.endRecord();
477                        out.writeEndElement(); // "recordData" element
478
479                        /*
480                         * recordIdentifier is version 1.2 only
481                         */
482                        if (request.isVersion(SRUVersion.VERSION_1_2)) {
483                            final String identifier =
484                                    result.getRecordIdentifier();
485                            if (identifier != null) {
486                                out.writeStartElement(SRU_NS,
487                                                      "recordIdentifier");
488                                out.writeCharacters(identifier);
489                                out.writeEndElement(); // "recordIdentifier" element
490                            }
491                        }
492
493                        out.writeStartElement(SRU_NS, "recordPosition");
494                        out.writeCharacters(Integer.toString(position));
495                        out.writeEndElement(); // "recordPosition" element
496
497                        if (result.hasExtraRecordData()) {
498                            out.writeStartElement(SRU_NS, "extraRecordData");
499                            result.writeExtraRecordData(out);
500                            out.writeEndElement(); // "extraRecordData"
501                        }
502
503                        out.writeEndElement(); // "record" element
504
505                        position++;
506                    } // while
507                    out.writeEndElement(); // "records" element
508                } catch (NoSuchElementException e) {
509                    throw new SRUException(
510                            SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
511                            "An internal error occurred while " +
512                            "serializing search result set.");
513                }
514            }
515
516            // nextRecordPosition
517            if (position <= result.getTotalRecordCount()) {
518                out.writeStartElement(SRU_NS, "nextRecordPosition");
519                out.writeCharacters(Integer.toString(position));
520                out.writeEndElement();
521            }
522
523            // echoedSearchRetrieveRequest
524            if (config.getEchoRequests()) {
525                writeEchoedSearchRetrieveRequest(out, request,
526                                                 request.getQuery());
527            }
528
529            // diagnostics
530            writeDiagnosticList(out, request.getDiagnostics());
531
532            // extraResponseData
533            if (result.hasExtraResponseData()) {
534                out.writeStartElement(SRU_NS, "extraResponseData");
535                result.writeExtraResponseData(out);
536                out.writeEndElement(); // "extraResponseData" element
537            }
538
539            endResponse(out);
540        } finally {
541            result.close();
542        }
543    }
544
545
546    private void beginResponse(SRUXMLStreamWriter out, SRUOperation operation,
547            SRUVersion version, String stylesheet) throws XMLStreamException {
548        out.writeStartDocument("utf-8", "1.0");
549
550        if (stylesheet != null) {
551            StringBuilder param = new StringBuilder();
552            param.append("type=\"text/xsl\"");
553            param.append(" ");
554            param.append("href=\"");
555            param.append(stylesheet);
556            param.append("\"");
557            out.writeProcessingInstruction("xml-stylesheet", param.toString());
558        }
559
560        out.setPrefix(SRU_PREFIX, SRU_NS);
561        switch (operation) {
562        case EXPLAIN:
563            out.writeStartElement(SRU_NS, "explainResponse");
564            break;
565        case SCAN:
566            out.writeStartElement(SRU_NS, "scanResponse");
567            break;
568        case SEARCH_RETRIEVE:
569            out.writeStartElement(SRU_NS, "searchRetrieveResponse");
570            break;
571        }
572        out.writeNamespace(SRU_PREFIX, SRU_NS);
573
574        // version
575        writeVersion(out, version);
576    }
577
578
579    private void beginResponse(SRUXMLStreamWriter out, SRURequest request)
580            throws XMLStreamException {
581        beginResponse(out, request.getOperation(), request.getVersion(),
582                request.getStylesheet());
583    }
584
585
586    private void endResponse(SRUXMLStreamWriter out)
587            throws XMLStreamException {
588        out.writeEndElement(); // "root" element
589
590        out.writeEndDocument();
591        out.close();
592        try {
593            out.getWriter().close();
594        } catch (IOException e) {
595            /* IGNORE */
596        }
597    }
598
599
600    private void writeFatalError(SRUXMLStreamWriter out,
601            SRURequestImpl request, List<SRUDiagnostic> diagnotics)
602            throws XMLStreamException {
603        /*
604         * if operation is unknown, default to 'explain'
605         */
606        SRUOperation operation = request.getOperation();
607        if (operation == null) {
608            operation = SRUOperation.EXPLAIN;
609        }
610        SRUVersion version = request.getVersion();
611        if (version == null) {
612            version = config.getDefaultVersion();
613        }
614        /*
615         * write a response which conforms to the schema
616         */
617        beginResponse(out, operation, version, null);
618        switch (operation) {
619        case EXPLAIN:
620            // 'explain' requires a complete explain record ...
621            writeExplainRecord(out, request);
622            break;
623        case SCAN:
624            // 'scan' fortunately does not need any elements ...
625            break;
626        case SEARCH_RETRIEVE:
627            // 'searchRetrieve' needs numberOfRecords ..
628            out.writeStartElement(SRU_NS, "numberOfRecords");
629            out.writeCharacters("0");
630            out.writeEndElement(); // "numberOfRecords" element
631            break;
632        }
633        writeDiagnosticList(out, diagnotics);
634        endResponse(out);
635    }
636
637
638    private void writeDiagnosticList(SRUXMLStreamWriter out,
639            List<SRUDiagnostic> diagnostics) throws XMLStreamException {
640        if ((diagnostics != null) && !diagnostics.isEmpty()) {
641            out.setPrefix(SRU_DIAGNOSTIC_PREFIX, SRU_DIAGNOSIC_NS);
642            out.writeStartElement(SRU_NS, "diagnostics");
643            out.writeNamespace(SRU_DIAGNOSTIC_PREFIX, SRU_DIAGNOSIC_NS);
644            for (SRUDiagnostic diagnostic : diagnostics) {
645                writeDiagnostic(out, diagnostic, false);
646            }
647            out.writeEndElement(); // "diagnostics" element
648        }
649    }
650
651
652    private void writeExplainRecord(SRUXMLStreamWriter out,
653            SRURequestImpl request) throws XMLStreamException {
654        out.writeStartElement(SRU_NS, "record");
655
656        out.writeStartElement(SRU_NS, "recordSchema");
657        out.writeCharacters(SRU_EXPLAIN_NS);
658        out.writeEndElement(); // "recordSchema" element
659
660        // recordPacking
661        writeRecordPacking(out, request.getRecordPacking());
662
663        out.writeStartElement(SRU_NS, "recordData");
664
665        out.startRecord();
666
667        // explain ...
668        out.setPrefix(SRU_EXPLAIN_PREFIX, SRU_EXPLAIN_NS);
669        out.writeStartElement(SRU_EXPLAIN_NS, "explain");
670        out.writeNamespace(SRU_EXPLAIN_PREFIX, SRU_EXPLAIN_NS);
671
672        // explain/serverInfo
673        out.writeStartElement(SRU_EXPLAIN_NS, "serverInfo");
674        out.writeAttribute("protocol", "SRU");
675        switch (config.getDefaultVersion()) {
676        case VERSION_1_1:
677            out.writeAttribute("version", "1.1");
678            break;
679        case VERSION_1_2:
680            out.writeAttribute("version", "1.2");
681        } // switch
682        out.writeAttribute("transport", config.getTransports());
683        out.writeStartElement(SRU_EXPLAIN_NS, "host");
684        out.writeCharacters(config.getHost());
685        out.writeEndElement(); // "host" element
686        out.writeStartElement(SRU_EXPLAIN_NS, "port");
687        out.writeCharacters(Integer.toString(config.getPort()));
688        out.writeEndElement(); // "port" element
689        out.writeStartElement(SRU_EXPLAIN_NS, "database");
690        out.writeCharacters(config.getDatabase());
691        out.writeEndElement(); // "database" element
692        out.writeEndElement(); // "serverInfo" element
693
694        // explain/databaseInfo
695        final DatabaseInfo dbinfo = config.getDatabaseInfo();
696        if (dbinfo != null) {
697            out.writeStartElement(SRU_EXPLAIN_NS, "databaseInfo");
698            writeLocalizedStrings(out, "title", dbinfo.getTitle());
699            writeLocalizedStrings(out, "description", dbinfo.getDescription());
700            writeLocalizedStrings(out, "author", dbinfo.getAuthor());
701            writeLocalizedStrings(out, "extent", dbinfo.getExtend());
702            writeLocalizedStrings(out, "history", dbinfo.getHistory());
703            writeLocalizedStrings(out, "langUsage", dbinfo.getLangUsage());
704            writeLocalizedStrings(out, "restrictions", dbinfo.getRestrictions());
705            writeLocalizedStrings(out, "subjects", dbinfo.getSubjects());
706            writeLocalizedStrings(out, "links", dbinfo.getLinks());
707            writeLocalizedStrings(out, "implementation",
708                    dbinfo.getImplementation());
709            out.writeEndElement(); // "databaseInfo" element
710        }
711
712        // explain/indexInfo
713        final IndexInfo indexInfo = config.getIndexInfo();
714        if (indexInfo != null) {
715            out.writeStartElement(SRU_EXPLAIN_NS, "indexInfo");
716
717            List<IndexInfo.Set> sets = indexInfo.getSets();
718            if (sets != null) {
719                for (IndexInfo.Set set : sets) {
720                    out.writeStartElement(SRU_EXPLAIN_NS, "set");
721                    out.writeAttribute("identifier", set.getIdentifier());
722                    out.writeAttribute("name", set.getName());
723                    writeLocalizedStrings(out, "title", set.getTitle());
724                    out.writeEndElement(); // "set" element
725                }
726            }
727
728            List<IndexInfo.Index> indexes = indexInfo.getIndexes();
729            if (indexes != null) {
730                for (IndexInfo.Index index : indexes) {
731                    out.writeStartElement(SRU_EXPLAIN_NS, "index");
732                    out.writeAttribute("search",
733                            index.canSearch() ? "true" : "false");
734                    out.writeAttribute("scan",
735                            index.canScan() ? "true" : "false");
736                    out.writeAttribute("sort",
737                            index.canSort() ? "true" : "false");
738                    writeLocalizedStrings(out, "title", index.getTitle());
739                    List<IndexInfo.Index.Map> maps = index.getMaps();
740                    if (maps != null) {
741                        for (IndexInfo.Index.Map map : maps) {
742                            out.writeStartElement(SRU_EXPLAIN_NS, "map");
743                            if (map.isPrimary()) {
744                                out.writeAttribute("primary", "true");
745                            }
746                            out.writeStartElement(SRU_EXPLAIN_NS, "name");
747                            out.writeAttribute("set", map.getSet());
748                            out.writeCharacters(map.getName());
749                            out.writeEndElement(); // "name" element
750                            out.writeEndElement(); // "map" element
751                        }
752                    }
753                    out.writeEndElement(); // "index" element
754                }
755            }
756            out.writeEndElement(); // "indexInfo" element
757        }
758
759        // explain/schemaInfo
760        final List<SchemaInfo> schemaInfo =
761                config.getSchemaInfo();
762        if (schemaInfo != null) {
763            out.writeStartElement(SRU_EXPLAIN_NS, "schemaInfo");
764            for (SRUServerConfig.SchemaInfo schema : schemaInfo) {
765                out.writeStartElement(SRU_EXPLAIN_NS, "schema");
766                out.writeAttribute("identifier", schema.getIdentifier());
767                out.writeAttribute("name", schema.getName());
768                /*
769                 * default is "false", so only add attribute if set to true
770                 */
771                if (schema.getSort() ) {
772                    out.writeAttribute("sort", "true");
773                }
774                /*
775                 * default is "true", so only add attribute if set to false
776                 */
777                if (!schema.getRetrieve()) {
778                    out.writeAttribute("retrieve", "false");
779                }
780                writeLocalizedStrings(out, "title", schema.getTitle());
781                out.writeEndElement(); // "schema" element
782            }
783            out.writeEndElement(); // "schemaInfo" element
784        }
785
786        // explain/configInfo
787        out.writeStartElement(SRU_EXPLAIN_NS, "configInfo");
788        // numberOfRecords (default)
789        out.writeStartElement(SRU_EXPLAIN_NS, "default");
790        out.writeAttribute("type", "numberOfRecords");
791        out.writeCharacters(Integer.toString(config.getNumberOfRecords()));
792        out.writeEndElement(); // default" element
793
794        // maximumRecords (setting)
795        out.writeStartElement(SRU_EXPLAIN_NS, "setting");
796        out.writeAttribute("type", "maximumRecords");
797        out.writeCharacters(Integer.toString(config.getMaximumRecords()));
798        out.writeEndElement(); // "setting" element
799
800        out.writeEndElement(); // "configInfo" element
801
802        out.writeEndElement(); // "explain" element
803
804        out.endRecord();
805
806        out.writeEndElement(); // "recordData" element
807        out.writeEndElement(); // "record" element
808    }
809
810
811    private void writeDiagnostic(SRUXMLStreamWriter out,
812            SRUDiagnostic diagnostic, boolean writeNsDecl)
813            throws XMLStreamException {
814        if (writeNsDecl) {
815            out.setPrefix(SRU_DIAGNOSTIC_PREFIX, SRU_DIAGNOSIC_NS);
816        }
817        out.writeStartElement(SRU_DIAGNOSIC_NS, "diagnostic");
818        if (writeNsDecl) {
819            out.writeNamespace(SRU_DIAGNOSTIC_PREFIX, SRU_DIAGNOSIC_NS);
820        }
821        out.writeStartElement(SRU_DIAGNOSIC_NS, "uri");
822        out.writeCharacters(SRUConstants.SRU_DIAGNOSTIC_URI_PREFIX);
823        out.writeCharacters(Integer.toString(diagnostic.getCode()));
824        out.writeEndElement(); // "uri" element
825        if (diagnostic.getDetails() != null) {
826            out.writeStartElement(SRU_DIAGNOSIC_NS, "details");
827            out.writeCharacters(diagnostic.getDetails());
828            out.writeEndElement(); // "details" element
829        }
830        if (diagnostic.getMessage() != null) {
831            out.writeStartElement(SRU_DIAGNOSIC_NS, "message");
832            out.writeCharacters(diagnostic.getMessage());
833            out.writeEndElement(); // "message" element
834        }
835        out.writeEndElement(); // "diagnostic" element
836    }
837
838
839    private void writeEchoedExplainRequest(SRUXMLStreamWriter out,
840            SRURequestImpl request) throws XMLStreamException,
841            SRUException {
842        // echoedSearchRetrieveRequest
843        out.writeStartElement(SRU_NS, "echoedExplainRequest");
844
845        // echoedExplainRequest/version
846        if (request.getRawVersion() != null) {
847            writeVersion(out, request.getRawVersion());
848        }
849
850        // echoedExplainRequest/recordPacking
851        if (request.getRawRecordPacking() != null) {
852            writeRecordPacking(out, request.getRawRecordPacking());
853        }
854
855        // echoedExplainRequest/stylesheet
856        if (request.getStylesheet() != null) {
857            out.writeStartElement(SRU_NS, "stylesheet");
858            out.writeCharacters(request.getStylesheet());
859            out.writeEndElement(); // "stylesheet" element
860        }
861
862        // echoedExplainRequest/baseUrl (SRU 1.2 only)
863        if (request.isVersion(SRUVersion.VERSION_1_2)) {
864            writeBaseUrl(out, request);
865        }
866
867        out.writeEndElement(); // "echoedExplainRequest" element
868    }
869
870
871    private void writeEchoedScanRequest(SRUXMLStreamWriter out,
872            SRURequestImpl request, CQLNode cql) throws XMLStreamException,
873            SRUException {
874        // echoedScanRequest
875        out.writeStartElement(SRU_NS, "echoedScanRequest");
876
877        // echoedScanRequest/version
878        if (request.getRawVersion() != null) {
879            writeVersion(out, request.getRawVersion());
880        }
881
882        // echoedScanRequest/scanClause
883        out.writeStartElement(SRU_NS, "scanClause");
884        out.writeCharacters(request.getRawScanClause());
885        out.writeEndElement(); // "query"
886
887        // echoedScanRequest/xScanClause
888        out.setDefaultNamespace(SRU_XCQL_NS);
889        out.writeStartElement(SRU_NS, "xScanClause");
890        out.writeDefaultNamespace(SRU_XCQL_NS);
891        out.writeXCQL(cql, false);
892        out.writeEndElement(); // "xScanClause" element
893
894        // echoedScanRequest/responsePosition
895        if (request.getResponsePosition() != -1) {
896            out.writeStartElement(SRU_NS, "responsePosition");
897            out.writeCharacters(
898                    Integer.toString(request.getResponsePosition()));
899            out.writeEndElement(); // "responsePosition" element
900        }
901
902        // echoedScanRequest/maximumTerms
903        if (request.getMaximumTerms() != -1) {
904            out.writeStartElement(SRU_NS, "maximumTerms");
905            out.writeCharacters(Integer.toString(request.getMaximumTerms()));
906            out.writeEndElement(); // "maximumTerms" element
907        }
908
909        // echoedScanRequest/stylesheet
910        if (request.getStylesheet() != null) {
911            out.writeStartElement(SRU_NS, "stylesheet");
912            out.writeCharacters(request.getStylesheet());
913            out.writeEndElement(); // "stylesheet" element
914        }
915
916        // echoedScanRequest/baseUrl (SRU 1.2 only)
917        if (request.isVersion(SRUVersion.VERSION_1_2)) {
918            writeBaseUrl(out, request);
919        }
920
921        out.writeEndElement(); // "echoedScanRequest" element
922    }
923
924
925    private void writeEchoedSearchRetrieveRequest(SRUXMLStreamWriter out,
926            SRURequestImpl request, CQLNode cql) throws XMLStreamException,
927            SRUException {
928        // echoedSearchRetrieveRequest
929        out.writeStartElement(SRU_NS, "echoedSearchRetrieveRequest");
930
931        // echoedSearchRetrieveRequest/version
932        if (request.getRawVersion() != null) {
933            writeVersion(out, request.getRawVersion());
934        }
935
936        // echoedSearchRetrieveRequest/query
937        out.writeStartElement(SRU_NS, "query");
938        out.writeCharacters(request.getRawQuery());
939        out.writeEndElement(); // "query"
940
941        // echoedSearchRetrieveRequest/xQuery
942        out.setDefaultNamespace(SRU_XCQL_NS);
943        out.writeStartElement(SRU_NS, "xQuery");
944        out.writeDefaultNamespace(SRU_XCQL_NS);
945        out.writeXCQL(cql, true);
946        out.writeEndElement(); // "xQuery" element
947
948        // echoedSearchRetrieveRequest/startRecord
949        if (request.getStartRecord() > 0) {
950            out.writeStartElement(SRU_NS, "startRecord");
951            out.writeCharacters(Integer.toString(request.getStartRecord()));
952            out.writeEndElement(); // "startRecord" element
953        }
954
955        // echoedSearchRetrieveRequest/maximumRecords
956        if (request.getRawMaximumRecords() > 0) {
957            out.writeStartElement(SRU_NS, "maximumRecords");
958            out.writeCharacters(
959                    Integer.toString(request.getRawMaximumRecords()));
960            out.writeEndElement(); // "startRecord" element
961        }
962
963        // echoedSearchRetrieveRequest/recordPacking
964        if (request.getRawRecordPacking() != null) {
965            writeRecordPacking(out, request.getRawRecordPacking());
966        }
967
968        // echoedSearchRetrieveRequest/recordSchema
969        if (request.getRecordSchemaName() != null) {
970            out.writeStartElement(SRU_NS, "recordSchema");
971            out.writeCharacters(request.getRecordSchemaName());
972            out.writeEndElement(); // "recordSchema" element
973        }
974
975        // echoedSearchRetrieveRequest/recordXPath (1.1)
976        if (request.isVersion(SRUVersion.VERSION_1_1) &&
977                (request.getRecordXPath() != null)) {
978            out.writeStartElement(SRU_NS, "recordXPath");
979            out.writeCharacters(request.getRecordXPath());
980            out.writeEndElement(); // "recordXPath" element
981        }
982
983        // echoedSearchRetrieveRequest/resultSetTTL
984        if (request.getResultSetTTL() > 0) {
985            out.writeStartElement(SRU_NS, "resultSetTTL");
986            out.writeCharacters(Long.toString(request.getResultSetTTL()));
987            out.writeEndElement(); // "resultSetTTL" element
988        }
989
990        // echoedSearchRetrieveRequest/sortKeys
991        if (request.isVersion(SRUVersion.VERSION_1_1) &&
992                (request.getSortKeys() != null)) {
993            out.writeStartElement(SRU_NS, "sortKeys");
994            out.writeCharacters(request.getSortKeys());
995            out.writeEndElement(); // "sortKeys" element
996        }
997
998        // echoedSearchRetrieveRequest/xsortKeys
999
1000        // echoedSearchRetrieveRequest/stylesheet
1001        if (request.getStylesheet() != null) {
1002            out.writeStartElement(SRU_NS, "stylesheet");
1003            out.writeCharacters(request.getStylesheet());
1004            out.writeEndElement(); // "stylesheet" element
1005        }
1006
1007        // echoedSearchRetrieveRequest/baseUrl (SRU 1.2 only)
1008        if (request.isVersion(SRUVersion.VERSION_1_2)) {
1009            writeBaseUrl(out, request);
1010        }
1011
1012        out.writeEndElement(); // "echoedSearchRetrieveRequest" element
1013    }
1014
1015
1016    private void writeVersion(SRUXMLStreamWriter out, SRUVersion version)
1017            throws XMLStreamException {
1018        out.writeStartElement(SRU_NS, "version");
1019        switch (version) {
1020        case VERSION_1_1:
1021            out.writeCharacters("1.1");
1022            break;
1023        case VERSION_1_2:
1024            out.writeCharacters("1.2");
1025            break;
1026        } // switch
1027        out.writeEndElement(); // "version" element
1028    }
1029
1030
1031    private void writeRecordPacking(SRUXMLStreamWriter out,
1032            SRURecordPacking recordPacking) throws XMLStreamException {
1033        out.writeStartElement(SRU_NS, "recordPacking");
1034        switch (recordPacking) {
1035        case XML:
1036            out.writeCharacters("xml");
1037            break;
1038        case STRING:
1039            out.writeCharacters("string");
1040            break;
1041        } // switch
1042        out.writeEndElement(); // "recordPacking" element
1043    }
1044
1045
1046    private void writeBaseUrl(SRUXMLStreamWriter out,
1047            SRURequest request) throws XMLStreamException {
1048        out.writeStartElement(SRU_NS, "baseUrl");
1049        out.writeCharacters(request.getProtocolScheme());
1050        out.writeCharacters(config.getBaseUrl());
1051        out.writeEndElement(); // "baseUrl" element
1052    }
1053
1054
1055    private void writeLocalizedStrings(XMLStreamWriter writer, String name,
1056            List<LocalizedString> list) throws XMLStreamException {
1057        if ((list != null) && !list.isEmpty()) {
1058            for (LocalizedString item : list) {
1059                writer.writeStartElement(SRU_EXPLAIN_NS, name);
1060                if (item.getLang() != null) {
1061                    writer.writeAttribute("lang", item.getLang());
1062                }
1063                if (item.isPrimary()) {
1064                    writer.writeAttribute("primary", "true");
1065                }
1066                writer.writeCharacters(item.getValue());
1067                writer.writeEndElement();
1068            }
1069        }
1070    }
1071
1072
1073    private SRUXMLStreamWriter createXMLStreamWriter(OutputStream out,
1074            SRURecordPacking recordPacking, boolean skipFlush, int indent)
1075            throws SRUException {
1076        try {
1077            if (skipFlush) {
1078                /*
1079                 * Add a FilterOutputStream to delay flush() as long as
1080                 * possible. Doing so, enabled us to send an appropriate SRU
1081                 * diagnostic in case an error occurs during the serialization
1082                 * of the response.
1083                 * Of course, if an error occurs when the Servlet response
1084                 * buffer already had been flushed, because it was to large,
1085                 * we cannot fail gracefully and we will produce ill-formed
1086                 * XML output.
1087                 */
1088                out = new FilterOutputStream(out) {
1089                    @Override
1090                    public void flush() throws IOException {
1091                    }
1092
1093
1094                    @Override
1095                    public void close() throws IOException {
1096                        super.flush();
1097                        super.close();
1098                    }
1099                };
1100            }
1101            return new SRUXMLStreamWriter(out, writerFactory,
1102                    recordPacking, indent);
1103        } catch (Exception e) {
1104            throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
1105                    "Error creating output stream.", e);
1106
1107        }
1108    }
1109
1110} // class SRUService
Note: See TracBrowser for help on using the repository browser.