source: SRUServer/tags/SRUServer-1.6.0/src/main/java/eu/clarin/sru/server/SRUServer.java @ 5925

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