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

Last change on this file since 6823 was 6823, checked in by Oliver Schonefeld, 9 years ago
  • fix resultSetTTL
  • Property svn:eol-style set to native
File size: 54.3 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_DIAGNOSTIC_RECORD_SCHEMA =
54            "info:srw/schema/1/diagnostics-v1.1";
55    static final String RESPONSE_ENCODING = "utf-8";
56    private static final String RESPONSE_CONTENT_TYPE = "application/xml";
57    private static final Logger logger =
58            LoggerFactory.getLogger(SRUServer.class);
59    private final SRUServerConfig config;
60    private final SRUSearchEngine searchEngine;
61    private final XMLOutputFactory writerFactory;
62
63
64    /**
65     * Constructor.
66     *
67     * @param config
68     *            a SRUEndpointConfig object
69     * @param searchEngine
70     *            an object implementing the SRUSearchEngine interface
71     * @throws NullPointerException
72     *             if config or searchEngine is <code>null</code>
73     * @throws SRUException
74     *             if an error occurred
75     */
76    public SRUServer(SRUServerConfig config, SRUSearchEngine searchEngine)
77            throws SRUException {
78        if (config == null) {
79            throw new NullPointerException("config == null");
80        }
81        this.config = config;
82        if (searchEngine == null) {
83            throw new NullPointerException("searchEngine == null");
84        }
85        this.searchEngine = searchEngine;
86        this.writerFactory = XMLOutputFactory.newInstance();
87    }
88
89
90    /**
91     * Handle a SRU request.
92     *
93     * @param request
94     *            a HttpServletRequest request
95     * @param response
96     *            a HttpServletResponse request
97     */
98    public void handleRequest(HttpServletRequest request,
99            HttpServletResponse response) {
100        final SRURequestImpl req = new SRURequestImpl(config, request);
101        try {
102            // set response properties
103            response.setContentType(RESPONSE_CONTENT_TYPE);
104            response.setCharacterEncoding(RESPONSE_ENCODING);
105            response.setStatus(HttpServletResponse.SC_OK);
106            // make sure we can reset the stream later in case of error ...
107            response.setBufferSize(config.getResponseBufferSize());
108            try {
109                if (req.checkParameters()) {
110                    switch (req.getOperation()) {
111                    case EXPLAIN:
112                        explain(req, response);
113                        break;
114                    case SCAN:
115                        scan(req, response);
116                        break;
117                    case SEARCH_RETRIEVE:
118                        search(req, response);
119                        break;
120                    }
121                } else {
122                    // (some) parameters are malformed, send error
123                    SRUXMLStreamWriter out =
124                        createXMLStreamWriter(response.getOutputStream(),
125                                SRURecordXmlEscaping.XML, false,
126                                req.getIndentResponse());
127                    final SRUNamespaces ns = getNamespaces(req.getVersion());
128                    writeFatalError(out, ns, req, req.getDiagnostics());
129                }
130            } catch (XMLStreamException e) {
131                logger.error("An error occurred while serializing response", e);
132                throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
133                        "An error occurred while serializing response.", e);
134            } catch (IOException e) {
135                /*
136                 * Well, can't really do anything useful here ...
137                 */
138                logger.error("An unexpected exception occurred", e);
139            }
140        } catch (SRUException e) {
141            if (!response.isCommitted()) {
142                if (logger.isInfoEnabled()) {
143                    final String message = e.getDiagnostic().getMessage();
144                    if (message != null) {
145                        logger.info("Sending fatal diagnostic '{}' with " +
146                                "message '{}'",
147                                e.getDiagnostic().getURI(),
148                                message);
149                    } else {
150                        logger.info("Sending fatal diagnostic '{}'",
151                                e.getDiagnostic().getURI());
152                    }
153                    logger.debug("Fatal diagnostic was caused by " +
154                            "this exception", e);
155                }
156                response.resetBuffer();
157                try {
158                    List<SRUDiagnostic> diagnostics = req.getDiagnostics();
159                    if (diagnostics != null) {
160                        diagnostics.add(e.getDiagnostic());
161                    } else {
162                        diagnostics = Arrays.asList(e.getDiagnostic());
163                    }
164                    SRUXMLStreamWriter out =
165                            createXMLStreamWriter(response.getOutputStream(),
166                                    SRURecordXmlEscaping.XML, false,
167                                    req.getIndentResponse());
168                    final SRUNamespaces ns = getNamespaces(req.getVersion());
169                    writeFatalError(out, ns, req, diagnostics);
170                } catch (Exception ex) {
171                    logger.error("An exception occurred while in error state",
172                            ex);
173                }
174            } else {
175                /*
176                 * The Servlet already flushed the output buffer, so cannot
177                 * degrade gracefully anymore and, unfortunately, will produce
178                 * ill-formed XML output.
179                 * Increase the response buffer size, if you want to avoid
180                 * this (at the cost of memory).
181                 */
182                logger.error("A fatal error occurred, but the response was "
183                        + "already committed. Unable to recover gracefully.", e);
184            }
185        }
186    }
187
188
189    private void explain(SRURequestImpl request, HttpServletResponse response)
190            throws IOException, XMLStreamException, SRUException {
191        logger.info("explain");
192
193        // commence explain ...
194        final SRUExplainResult result =
195                searchEngine.explain(config, request, request);
196
197        try {
198            final SRUNamespaces ns = getNamespaces(request.getVersion());
199
200            // send results
201            SRUXMLStreamWriter out =
202                    createXMLStreamWriter(response.getOutputStream(),
203                                          request.getRecordXmlEscaping(),
204                                          true,
205                                          request.getIndentResponse());
206
207            beginResponse(out, ns, request);
208
209            // write the explain record
210            writeExplainRecord(out, ns, request);
211
212            if (config.getEchoRequests()) {
213                writeEchoedExplainRequest(out, ns, request);
214            }
215
216            // diagnostics
217            writeDiagnosticList(out, ns, request.getDiagnostics());
218
219            // extraResponseData
220            if (result != null) {
221                if (result.hasExtraResponseData()) {
222                    out.writeStartElement(ns.getResponseNS(), "extraResponseData");
223                    result.writeExtraResponseData(out);
224                    out.writeEndElement(); // "extraResponseData" element
225                }
226            }
227
228            endResponse(out);
229        } finally {
230            if (result != null) {
231                result.close();
232            }
233        }
234    }
235
236
237    private void scan(SRURequestImpl request, HttpServletResponse response)
238            throws IOException, XMLStreamException, SRUException {
239        logger.info("scan: scanClause = \"{}\"",
240                new Object[] { request.getRawScanClause() });
241
242        // commence scan
243        final SRUScanResultSet result =
244                searchEngine.scan(config, request, request);
245        if (result == null) {
246            throw new SRUException(SRUConstants.SRU_UNSUPPORTED_OPERATION,
247                    "The 'scan' operation is not supported by this endpoint.");
248        }
249
250        try {
251            final SRUNamespaces ns = getNamespaces(request.getVersion());
252
253            /*
254             * FIXME: re-check, if while scan response needs to be put
255             * in scan namespace for SRU 2.0!
256             */
257            // send results
258            SRUXMLStreamWriter out =
259                    createXMLStreamWriter(response.getOutputStream(),
260                                          request.getRecordXmlEscaping(),
261                                          true,
262                                          request.getIndentResponse());
263
264            beginResponse(out, ns, request);
265
266            try {
267                /*
268                 * a scan result without a list of terms is a valid response;
269                 * make sure, to produce the correct output and omit in that case
270                 * the <terms> ...
271                 */
272                boolean wroteTerms = false;
273                while (result.nextTerm()) {
274                    if (!wroteTerms) {
275                        final boolean needNsDecl =
276                                !ns.getResponseNS().equals(ns.getScanNS());
277                        if (needNsDecl) {
278                            out.setPrefix(ns.getScanPrefix(), ns.getScanNS());
279                        }
280                        out.writeStartElement(ns.getScanNS(), "terms");
281                        if (needNsDecl) {
282                            out.writeNamespace(ns.getScanPrefix(),
283                                    ns.getScanNS());
284                        }
285                        wroteTerms = true;
286                    }
287                    out.writeStartElement(ns.getScanNS(), "term");
288
289                    out.writeStartElement(ns.getScanNS(), "value");
290                    out.writeCharacters(result.getValue());
291                    out.writeEndElement(); // "value" element
292
293                    if (result.getNumberOfRecords() > -1) {
294                        out.writeStartElement(ns.getScanNS(), "numberOfRecords");
295                        out.writeCharacters(
296                                Integer.toString(result.getNumberOfRecords()));
297                        out.writeEndElement(); // "numberOfRecords" element
298                    }
299
300                    if (result.getDisplayTerm() != null) {
301                        out.writeStartElement(ns.getScanNS(), "displayTerm");
302                        out.writeCharacters(result.getDisplayTerm());
303                        out.writeEndElement(); // "displayTerm" element
304                    }
305
306                    if (result.getWhereInList() != null) {
307                        out.writeStartElement(ns.getScanNS(), "whereInList");
308                        switch (result.getWhereInList()) {
309                        case FIRST:
310                            out.writeCharacters("first");
311                            break;
312                        case LAST:
313                            out.writeCharacters("last");
314                            break;
315                        case ONLY:
316                            out.writeCharacters("only");
317                            break;
318                        case INNER:
319                            out.writeCharacters("inner");
320                            break;
321                        } // switch
322                        out.writeEndElement(); // "whereInList" element
323                    }
324
325                    if (result.hasExtraTermData()) {
326                        out.writeStartElement(ns.getScanNS(), "extraTermData");
327                        result.writeExtraTermData(out);
328                        out.writeEndElement(); // "extraTermData" element
329                    }
330
331                    out.writeEndElement(); // "term" element
332                } // while
333                if (wroteTerms) {
334                    out.writeEndElement(); // "terms" element
335                }
336            } catch (NoSuchElementException e) {
337                throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
338                        "An internal error occurred while "
339                                + "serializing scan results.");
340            }
341
342            // echoedScanRequest
343            if (config.getEchoRequests()) {
344                writeEchoedScanRequest(out, ns, request, request.getScanClause());
345            }
346
347            // diagnostics
348            writeDiagnosticList(out, ns, request.getDiagnostics());
349
350            // extraResponseData
351            if (result.hasExtraResponseData()) {
352                out.writeStartElement(ns.getResponseNS(), "extraResponseData");
353                result.writeExtraResponseData(out);
354                out.writeEndElement(); // "extraResponseData" element
355            }
356
357            endResponse(out);
358        } finally {
359            result.close();
360        }
361    }
362
363
364    private void search(SRURequestImpl request, HttpServletResponse response)
365            throws IOException, XMLStreamException, SRUException {
366        logger.info("searchRetrieve: query = \"{}\", startRecord = {}, " +
367                "maximumRecords = {}, recordSchema = {}, resultSetTTL = {}",
368                new Object[] { request.getRawQuery(), request.getStartRecord(),
369                        request.getMaximumRecords(),
370                        request.getRecordSchemaIdentifier(),
371                        request.getResultSetTTL() });
372
373        // commence search ...
374        final SRUSearchResultSet result =
375                searchEngine.search(config, request, request);
376        if (result == null) {
377            throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
378                    "SRUSearchEngine implementation returned invalid result (null).");
379        }
380
381
382        // check, of startRecord position is greater than total record set
383        if ((result.getTotalRecordCount() >= 0) &&
384            (request.getStartRecord() > 1) &&
385            (request.getStartRecord() > result.getTotalRecordCount())) {
386            throw new SRUException(
387                    SRUConstants.SRU_FIRST_RECORD_POSITION_OUT_OF_RANGE);
388        }
389
390        try {
391            final SRUNamespaces ns = getNamespaces(request.getVersion());
392
393            // send results
394            SRUXMLStreamWriter out =
395                    createXMLStreamWriter(response.getOutputStream(),
396                                          request.getRecordXmlEscaping(),
397                                          true,
398                                          request.getIndentResponse());
399
400            beginResponse(out, ns, request);
401
402            // numberOfRecords
403            out.writeStartElement(ns.getResponseNS(), "numberOfRecords");
404            out.writeCharacters(
405                    Integer.toString(result.getTotalRecordCount()));
406            out.writeEndElement(); // "numberOfRecords" element
407
408            // resultSetId
409            if (result.getResultSetId() != null) {
410                out.writeStartElement(ns.getResponseNS(), "resultSetId");
411                out.writeCharacters(result.getResultSetId());
412                out.writeEndElement(); // "resultSetId" element
413            }
414
415            // resultSetIdleTime (SRU 1.1 and SRU 1.2)
416            if (!request.isVersion(SRUVersion.VERSION_2_0) &&
417                    (result.getResultSetTTL() >= 0)) {
418                out.writeStartElement(ns.getResponseNS(), "resultSetIdleTime");
419                out.writeCharacters(Integer.toString(result
420                        .getResultSetTTL()));
421                out.writeEndElement(); // "resultSetIdleTime" element
422            }
423
424            int position = (request.getStartRecord() > 0)
425                    ? request.getStartRecord() : 1;
426            if (result.getRecordCount() > 0) {
427                final int maxPositionOffset =
428                        (request.getMaximumRecords() != -1)
429                        ? (position + request.getMaximumRecords() - 1)
430                        : -1;
431                try {
432                    out.writeStartElement(ns.getResponseNS(), "records");
433                    while (result.nextRecord()) {
434                        /*
435                         * Sanity check: do not return more then the maximum
436                         * requested records. If the search engine
437                         * implementation does not honor limit truncate the
438                         * result set.
439                         */
440                        if ((maxPositionOffset != -1) &&
441                                (position > maxPositionOffset)) {
442                            logger.error("SRUSearchEngine implementation did " +
443                                    "not honor limit for the amount of " +
444                                    "requsted records. Result set truncated!");
445                            break;
446                        }
447
448                        out.writeStartElement(ns.getResponseNS(), "record");
449
450                        /*
451                         * We need to output either the record or a surrogate
452                         * diagnostic. In case of the latter, we need to output
453                         * the appropriate record schema ...
454                         */
455                        final SRUDiagnostic diagnostic =
456                                result.getSurrogateDiagnostic();
457
458                        out.writeStartElement(ns.getResponseNS(), "recordSchema");
459                        if (diagnostic == null) {
460                            out.writeCharacters(
461                                    result.getRecordSchemaIdentifier());
462                        } else {
463                            out.writeCharacters(SRU_DIAGNOSTIC_RECORD_SCHEMA);
464                        }
465                        out.writeEndElement(); // "recordSchema" element
466
467                        // recordPacking
468                        writeRecordPacking(out, ns, request.getRecordXmlEscaping());
469
470                        /*
471                         * Output either record data or surrogate diagnostic ...
472                         */
473                        out.writeStartElement(ns.getResponseNS(), "recordData");
474                        out.startRecord();
475                        if (diagnostic == null) {
476                            result.writeRecord(out);
477                        } else {
478                            // write a surrogate diagnostic
479                            writeDiagnostic(out, ns, diagnostic, true);
480                        }
481                        out.endRecord();
482                        out.writeEndElement(); // "recordData" element
483
484                        /*
485                         * recordIdentifier is version 1.2+ only
486                         */
487                        if (request.isVersion(SRUVersion.VERSION_1_2,
488                                SRUVersion.VERSION_2_0)) {
489                            final String identifier =
490                                    result.getRecordIdentifier();
491                            if (identifier != null) {
492                                out.writeStartElement(ns.getResponseNS(),
493                                                      "recordIdentifier");
494                                out.writeCharacters(identifier);
495                                out.writeEndElement(); // "recordIdentifier" element
496                            }
497                        }
498
499                        out.writeStartElement(ns.getResponseNS(),
500                                "recordPosition");
501                        out.writeCharacters(Integer.toString(position));
502                        out.writeEndElement(); // "recordPosition" element
503
504                        if (result.hasExtraRecordData()) {
505                            out.writeStartElement(ns.getResponseNS(),
506                                    "extraRecordData");
507                            result.writeExtraRecordData(out);
508                            out.writeEndElement(); // "extraRecordData"
509                        }
510
511                        out.writeEndElement(); // "record" element
512
513                        position++;
514                    } // while
515                    out.writeEndElement(); // "records" element
516                } catch (NoSuchElementException e) {
517                    throw new SRUException(
518                            SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
519                            "An internal error occurred while " +
520                            "serializing search result set.");
521                }
522            }
523
524            // nextRecordPosition
525            if (position <= result.getTotalRecordCount()) {
526                out.writeStartElement(ns.getResponseNS(), "nextRecordPosition");
527                out.writeCharacters(Integer.toString(position));
528                out.writeEndElement();
529            }
530
531            // echoedSearchRetrieveRequest
532            if (config.getEchoRequests()) {
533                writeEchoedSearchRetrieveRequest(out, ns, request,
534                                                 request.getQuery());
535            }
536
537            // diagnostics
538            writeDiagnosticList(out, ns, request.getDiagnostics());
539
540            // extraResponseData
541            if (result.hasExtraResponseData()) {
542                out.writeStartElement(ns.getResponseNS(), "extraResponseData");
543                result.writeExtraResponseData(out);
544                out.writeEndElement(); // "extraResponseData" element
545            }
546
547            // SRU 2.0 stuff ...
548            if (request.isVersion(SRUVersion.VERSION_2_0)) {
549                // resultSetTTL
550                if (result.getResultSetTTL() >= 0) {
551                    out.writeStartElement(ns.getResponseNS(), "resultSetTTL");
552                    out.writeCharacters(
553                            Integer.toString(result.getResultSetTTL()));
554                    out.writeEndElement(); // "resultSetTTL" element
555                }
556
557                // resultCountPrecision
558                final SRUResultCountPrecision precision =
559                        result.getResultCountPrecision();
560                if (precision != null) {
561                    out.writeStartElement(ns.getResponseNS(), "resultCountPrecision");
562                    switch (precision) {
563                    case EXACT:
564                        out.writeCharacters("info:srw/vocabulary/resultCountPrecision/1/exact");
565                        break;
566                    case UNKNOWN:
567                        out.writeCharacters("info:srw/vocabulary/resultCountPrecision/1/unknown");
568                        break;
569                    case ESTIMATE:
570                        out.writeCharacters("info:srw/vocabulary/resultCountPrecision/1/estimate");
571                        break;
572                    case MAXIMUM:
573                        out.writeCharacters("info:srw/vocabulary/resultCountPrecision/1/maximum");
574                        break;
575                    case MINIMUM:
576                        out.writeCharacters("info:srw/vocabulary/resultCountPrecision/1/minimum");
577                        break;
578                    case CURRENT:
579                        out.writeCharacters("info:srw/vocabulary/resultCountPrecision/1/current");
580                        break;
581                    } // switch
582                    out.writeEndElement(); // "resultCountPrecision" element
583                }
584
585                // facetedResults
586                // NOT YET SUPPORTED
587
588                // searchResultAnalysis
589                // NOT YET SUPPORTED
590            }
591            endResponse(out);
592        } finally {
593            result.close();
594        }
595    }
596
597
598    private void beginResponse(SRUXMLStreamWriter out, SRUNamespaces ns,
599            SRUOperation operation, SRUVersion version, String stylesheet)
600            throws XMLStreamException {
601        out.writeStartDocument("utf-8", "1.0");
602
603        if (stylesheet != null) {
604            StringBuilder param = new StringBuilder();
605            param.append("type=\"text/xsl\"");
606            param.append(" ");
607            param.append("href=\"");
608            param.append(stylesheet);
609            param.append("\"");
610            out.writeProcessingInstruction("xml-stylesheet", param.toString());
611        }
612
613        out.setPrefix(ns.getResponsePrefix(), ns.getResponseNS());
614        switch (operation) {
615        case EXPLAIN:
616            out.writeStartElement(ns.getResponseNS(), "explainResponse");
617            break;
618        case SCAN:
619            out.writeStartElement(ns.getResponseNS(), "scanResponse");
620            break;
621        case SEARCH_RETRIEVE:
622            out.writeStartElement(ns.getResponseNS(), "searchRetrieveResponse");
623            break;
624        }
625        out.writeNamespace(ns.getResponsePrefix(), ns.getResponseNS());
626
627        // version
628        writeVersion(out, ns, version);
629    }
630
631
632    private void beginResponse(SRUXMLStreamWriter out, SRUNamespaces ns,
633            SRURequest request) throws XMLStreamException {
634        beginResponse(out, ns, request.getOperation(), request.getVersion(),
635                request.getStylesheet());
636    }
637
638
639    private void endResponse(SRUXMLStreamWriter out)
640            throws XMLStreamException {
641        out.writeEndElement(); // "root" element
642
643        out.writeEndDocument();
644        out.close();
645        try {
646            out.getWriter().close();
647        } catch (IOException e) {
648            /* IGNORE */
649        }
650    }
651
652
653    private void writeFatalError(SRUXMLStreamWriter out, SRUNamespaces ns,
654            SRURequestImpl request, List<SRUDiagnostic> diagnotics)
655            throws XMLStreamException {
656        /*
657         * if operation is unknown, default to 'explain'
658         */
659        SRUOperation operation = request.getOperation();
660        if (operation == null) {
661            operation = SRUOperation.EXPLAIN;
662        }
663        SRUVersion version = request.getVersion();
664        if (version == null) {
665            version = config.getDefaultVersion();
666        }
667        /*
668         * write a response which conforms to the schema
669         */
670        beginResponse(out, ns, operation, version, null);
671        switch (operation) {
672        case EXPLAIN:
673            // 'explain' requires a complete explain record ...
674            writeExplainRecord(out, ns, request);
675            break;
676        case SCAN:
677            // 'scan' fortunately does not need any elements ...
678            break;
679        case SEARCH_RETRIEVE:
680            // 'searchRetrieve' needs numberOfRecords ..
681            out.writeStartElement(ns.getResponseNS(), "numberOfRecords");
682            out.writeCharacters("0");
683            out.writeEndElement(); // "numberOfRecords" element
684            break;
685        }
686        writeDiagnosticList(out, ns, diagnotics);
687        endResponse(out);
688    }
689
690
691    private void writeDiagnosticList(SRUXMLStreamWriter out, SRUNamespaces ns,
692            List<SRUDiagnostic> diagnostics) throws XMLStreamException {
693        if ((diagnostics != null) && !diagnostics.isEmpty()) {
694            out.setPrefix(ns.getDiagnosticPrefix(), ns.getDiagnosticNS());
695            out.writeStartElement(ns.getDiagnosticNS(), "diagnostics");
696            out.writeNamespace(ns.getDiagnosticPrefix(), ns.getDiagnosticNS());
697            for (SRUDiagnostic diagnostic : diagnostics) {
698                writeDiagnostic(out, ns, diagnostic, false);
699            }
700            out.writeEndElement(); // "diagnostics" element
701        }
702    }
703
704
705    private void writeExplainRecord(SRUXMLStreamWriter out, SRUNamespaces ns,
706            SRURequestImpl request) throws XMLStreamException {
707        out.writeStartElement(ns.getResponseNS(), "record");
708
709        out.writeStartElement(ns.getResponseNS(), "recordSchema");
710        out.writeCharacters(ns.getExplainNS());
711        out.writeEndElement(); // "recordSchema" element
712
713        // recordPacking
714        writeRecordPacking(out, ns, request.getRecordXmlEscaping());
715
716        out.writeStartElement(ns.getResponseNS(), "recordData");
717
718        out.startRecord();
719
720        // explain ...
721        out.setPrefix(ns.getExplainPrefix(), ns.getExplainNS());
722        out.writeStartElement(ns.getExplainNS(), "explain");
723        out.writeNamespace(ns.getExplainPrefix(), ns.getExplainNS());
724
725        // explain/serverInfo
726        out.writeStartElement(ns.getExplainNS(), "serverInfo");
727        out.writeAttribute("protocol", "SRU");
728        switch (config.getDefaultVersion()) {
729        case VERSION_1_1:
730            out.writeAttribute("version", "1.1");
731            break;
732        case VERSION_1_2:
733            out.writeAttribute("version", "1.2");
734            break;
735        case VERSION_2_0:
736            out.writeAttribute("version", "2.0");
737            break;
738        } // switch
739        out.writeAttribute("transport", config.getTransports());
740        out.writeStartElement(ns.getExplainNS(), "host");
741        out.writeCharacters(config.getHost());
742        out.writeEndElement(); // "host" element
743        out.writeStartElement(ns.getExplainNS(), "port");
744        out.writeCharacters(Integer.toString(config.getPort()));
745        out.writeEndElement(); // "port" element
746        out.writeStartElement(ns.getExplainNS(), "database");
747        out.writeCharacters(config.getDatabase());
748        out.writeEndElement(); // "database" element
749        out.writeEndElement(); // "serverInfo" element
750
751        // explain/databaseInfo
752        final DatabaseInfo dbinfo = config.getDatabaseInfo();
753        if (dbinfo != null) {
754            out.writeStartElement(ns.getExplainNS(), "databaseInfo");
755            writeLocalizedStrings(out, ns, "title",
756                    dbinfo.getTitle());
757            writeLocalizedStrings(out, ns, "description",
758                    dbinfo.getDescription());
759            writeLocalizedStrings(out, ns, "author",
760                    dbinfo.getAuthor());
761            writeLocalizedStrings(out, ns, "extent",
762                    dbinfo.getExtend());
763            writeLocalizedStrings(out, ns, "history",
764                    dbinfo.getHistory());
765            writeLocalizedStrings(out, ns, "langUsage",
766                    dbinfo.getLangUsage());
767            writeLocalizedStrings(out, ns, "restrictions",
768                    dbinfo.getRestrictions());
769            writeLocalizedStrings(out, ns, "subjects",
770                    dbinfo.getSubjects());
771            writeLocalizedStrings(out, ns, "links",
772                    dbinfo.getLinks());
773            writeLocalizedStrings(out, ns, "implementation",
774                    dbinfo.getImplementation());
775            out.writeEndElement(); // "databaseInfo" element
776        }
777
778        // explain/indexInfo
779        final IndexInfo indexInfo = config.getIndexInfo();
780        if (indexInfo != null) {
781            out.writeStartElement(ns.getExplainNS(), "indexInfo");
782
783            List<IndexInfo.Set> sets = indexInfo.getSets();
784            if (sets != null) {
785                for (IndexInfo.Set set : sets) {
786                    out.writeStartElement(ns.getExplainNS(), "set");
787                    out.writeAttribute("identifier", set.getIdentifier());
788                    out.writeAttribute("name", set.getName());
789                    writeLocalizedStrings(out, ns, "title", set.getTitle());
790                    out.writeEndElement(); // "set" element
791                }
792            }
793
794            List<IndexInfo.Index> indexes = indexInfo.getIndexes();
795            if (indexes != null) {
796                for (IndexInfo.Index index : indexes) {
797                    out.writeStartElement(ns.getExplainNS(), "index");
798                    out.writeAttribute("search",
799                            index.canSearch() ? "true" : "false");
800                    out.writeAttribute("scan",
801                            index.canScan() ? "true" : "false");
802                    out.writeAttribute("sort",
803                            index.canSort() ? "true" : "false");
804                    writeLocalizedStrings(out, ns, "title", index.getTitle());
805                    List<IndexInfo.Index.Map> maps = index.getMaps();
806                    if (maps != null) {
807                        for (IndexInfo.Index.Map map : maps) {
808                            out.writeStartElement(ns.getExplainNS(), "map");
809                            if (map.isPrimary()) {
810                                out.writeAttribute("primary", "true");
811                            }
812                            out.writeStartElement(ns.getExplainNS(), "name");
813                            out.writeAttribute("set", map.getSet());
814                            out.writeCharacters(map.getName());
815                            out.writeEndElement(); // "name" element
816                            out.writeEndElement(); // "map" element
817                        }
818                    }
819                    out.writeEndElement(); // "index" element
820                }
821            }
822            out.writeEndElement(); // "indexInfo" element
823        }
824
825        // explain/schemaInfo
826        final List<SchemaInfo> schemaInfo =
827                config.getSchemaInfo();
828        if (schemaInfo != null) {
829            out.writeStartElement(ns.getExplainNS(), "schemaInfo");
830            for (SRUServerConfig.SchemaInfo schema : schemaInfo) {
831                out.writeStartElement(ns.getExplainNS(), "schema");
832                out.writeAttribute("identifier", schema.getIdentifier());
833                out.writeAttribute("name", schema.getName());
834                /*
835                 * default is "false", so only add attribute if set to true
836                 */
837                if (schema.getSort() ) {
838                    out.writeAttribute("sort", "true");
839                }
840                /*
841                 * default is "true", so only add attribute if set to false
842                 */
843                if (!schema.getRetrieve()) {
844                    out.writeAttribute("retrieve", "false");
845                }
846                writeLocalizedStrings(out, ns, "title", schema.getTitle());
847                out.writeEndElement(); // "schema" element
848            }
849            out.writeEndElement(); // "schemaInfo" element
850        }
851
852        // explain/configInfo
853        out.writeStartElement(ns.getExplainNS(), "configInfo");
854        // numberOfRecords (default)
855        out.writeStartElement(ns.getExplainNS(), "default");
856        out.writeAttribute("type", "numberOfRecords");
857        out.writeCharacters(Integer.toString(config.getNumberOfRecords()));
858        out.writeEndElement(); // default" element
859
860        // maximumRecords (setting)
861        out.writeStartElement(ns.getExplainNS(), "setting");
862        out.writeAttribute("type", "maximumRecords");
863        out.writeCharacters(Integer.toString(config.getMaximumRecords()));
864        out.writeEndElement(); // "setting" element
865
866        out.writeEndElement(); // "configInfo" element
867
868        out.writeEndElement(); // "explain" element
869
870        out.endRecord();
871
872        out.writeEndElement(); // "recordData" element
873        out.writeEndElement(); // "record" element
874    }
875
876
877    private void writeDiagnostic(SRUXMLStreamWriter out, SRUNamespaces ns,
878            SRUDiagnostic diagnostic, boolean writeNsDecl)
879            throws XMLStreamException {
880        if (writeNsDecl) {
881            out.setPrefix(ns.getDiagnosticPrefix(), ns.getDiagnosticNS());
882        }
883        out.writeStartElement(ns.getDiagnosticNS(), "diagnostic");
884        if (writeNsDecl) {
885            out.writeNamespace(ns.getDiagnosticPrefix(), ns.getDiagnosticNS());
886        }
887        out.writeStartElement(ns.getDiagnosticNS(), "uri");
888        out.writeCharacters(diagnostic.getURI());
889        out.writeEndElement(); // "uri" element
890        if (diagnostic.getDetails() != null) {
891            out.writeStartElement(ns.getDiagnosticNS(), "details");
892            out.writeCharacters(diagnostic.getDetails());
893            out.writeEndElement(); // "details" element
894        }
895        if (diagnostic.getMessage() != null) {
896            out.writeStartElement(ns.getDiagnosticNS(), "message");
897            out.writeCharacters(diagnostic.getMessage());
898            out.writeEndElement(); // "message" element
899        }
900        out.writeEndElement(); // "diagnostic" element
901    }
902
903
904    private void writeEchoedExplainRequest(SRUXMLStreamWriter out,
905            SRUNamespaces ns, SRURequestImpl request)
906            throws XMLStreamException, SRUException {
907        // echoedSearchRetrieveRequest
908        out.writeStartElement(ns.getResponseNS(), "echoedExplainRequest");
909
910        // echoedExplainRequest/version
911        if (request.getRawVersion() != null) {
912            writeVersion(out, ns, request.getRawVersion());
913        }
914
915        // echoedExplainRequest/recordPacking
916        if (request.getRawRecordPacking() != null) {
917            writeRecordPacking(out, ns, request.getRawRecordPacking());
918        }
919
920        // echoedExplainRequest/stylesheet
921        if (request.getStylesheet() != null) {
922            out.writeStartElement(ns.getResponseNS(), "stylesheet");
923            out.writeCharacters(request.getStylesheet());
924            out.writeEndElement(); // "stylesheet" element
925        }
926
927        out.writeEndElement(); // "echoedExplainRequest" element
928    }
929
930
931    private void writeEchoedScanRequest(SRUXMLStreamWriter out,
932            SRUNamespaces ns, SRURequestImpl request, CQLNode cql)
933            throws XMLStreamException, SRUException {
934        // echoedScanRequest
935        out.writeStartElement(ns.getResponseNS(), "echoedScanRequest");
936
937        // echoedScanRequest/version
938        if (request.getRawVersion() != null) {
939            writeVersion(out, ns, request.getRawVersion());
940        }
941
942        // echoedScanRequest/scanClause
943        out.writeStartElement(ns.getResponseNS(), "scanClause");
944        out.writeCharacters(request.getRawScanClause());
945        out.writeEndElement(); // "query"
946
947        // echoedScanRequest/xScanClause
948        out.setDefaultNamespace(ns.getXcqlNS());
949        out.writeStartElement(ns.getResponseNS(), "xScanClause");
950        out.writeDefaultNamespace(ns.getXcqlNS());
951        out.writeXCQL(cql, false);
952        out.writeEndElement(); // "xScanClause" element
953
954        // echoedScanRequest/responsePosition
955        if (request.getResponsePosition() != -1) {
956            out.writeStartElement(ns.getResponseNS(), "responsePosition");
957            out.writeCharacters(
958                    Integer.toString(request.getResponsePosition()));
959            out.writeEndElement(); // "responsePosition" element
960        }
961
962        // echoedScanRequest/maximumTerms
963        if (request.getMaximumTerms() != -1) {
964            out.writeStartElement(ns.getResponseNS(), "maximumTerms");
965            out.writeCharacters(Integer.toString(request.getMaximumTerms()));
966            out.writeEndElement(); // "maximumTerms" element
967        }
968
969        // echoedScanRequest/stylesheet
970        if (request.getStylesheet() != null) {
971            out.writeStartElement(ns.getResponseNS(), "stylesheet");
972            out.writeCharacters(request.getStylesheet());
973            out.writeEndElement(); // "stylesheet" element
974        }
975
976        out.writeEndElement(); // "echoedScanRequest" element
977    }
978
979
980    private void writeEchoedSearchRetrieveRequest(SRUXMLStreamWriter out,
981            SRUNamespaces ns, SRURequestImpl request, SRUQuery<?> query)
982            throws XMLStreamException, SRUException {
983        // echoedSearchRetrieveRequest
984        out.writeStartElement(ns.getResponseNS(),
985                "echoedSearchRetrieveRequest");
986
987        // echoedSearchRetrieveRequest/version
988        if (request.getRawVersion() != null) {
989            writeVersion(out, ns, request.getRawVersion());
990        }
991
992        /*
993         * XXX: unclear, if <query> should only be echoed if queryType is CQL!?
994         */
995        if (SRUConstants.SRU_QUERY_TYPE_CQL.equals(query.getQueryType())) {
996            final CQLQueryParser.CQLQuery cql = (CQLQueryParser.CQLQuery) query;
997            // echoedSearchRetrieveRequest/query
998            out.writeStartElement(ns.getResponseNS(), "query");
999            out.writeCharacters(cql.getRawQuery());
1000            out.writeEndElement(); // "query"
1001
1002            // echoedSearchRetrieveRequest/xQuery
1003            out.setDefaultNamespace(ns.getXcqlNS());
1004            out.writeStartElement(ns.getResponseNS(), "xQuery");
1005            out.writeDefaultNamespace(ns.getXcqlNS());
1006            out.writeXCQL(cql.getParsedQuery(), true);
1007            out.writeEndElement(); // "xQuery" element
1008        }
1009
1010        // echoedSearchRetrieveRequest/startRecord
1011        if (request.getStartRecord() > 0) {
1012            out.writeStartElement(ns.getResponseNS(), "startRecord");
1013            out.writeCharacters(Integer.toString(request.getStartRecord()));
1014            out.writeEndElement(); // "startRecord" element
1015        }
1016
1017        // echoedSearchRetrieveRequest/maximumRecords
1018        if (request.getRawMaximumRecords() > 0) {
1019            out.writeStartElement(ns.getResponseNS(), "maximumRecords");
1020            out.writeCharacters(
1021                    Integer.toString(request.getRawMaximumRecords()));
1022            out.writeEndElement(); // "startRecord" element
1023        }
1024
1025        // echoedSearchRetrieveRequest/recordPacking
1026        if (request.getRawRecordPacking() != null) {
1027            writeRecordPacking(out, ns, request.getRawRecordPacking());
1028        }
1029
1030        // echoedSearchRetrieveRequest/recordSchema
1031        if (request.getRawRecordSchemaIdentifier() != null) {
1032            out.writeStartElement(ns.getResponseNS(), "recordSchema");
1033            out.writeCharacters(request.getRawRecordSchemaIdentifier());
1034            out.writeEndElement(); // "recordSchema" element
1035        }
1036
1037        // echoedSearchRetrieveRequest/recordXPath (1.1)
1038        if (request.isVersion(SRUVersion.VERSION_1_1) &&
1039                (request.getRecordXPath() != null)) {
1040            out.writeStartElement(ns.getResponseNS(), "recordXPath");
1041            out.writeCharacters(request.getRecordXPath());
1042            out.writeEndElement(); // "recordXPath" element
1043        }
1044
1045        // echoedSearchRetrieveRequest/resultSetTTL
1046        if (request.getResultSetTTL() > 0) {
1047            out.writeStartElement(ns.getResponseNS(), "resultSetTTL");
1048            out.writeCharacters(Long.toString(request.getResultSetTTL()));
1049            out.writeEndElement(); // "resultSetTTL" element
1050        }
1051
1052        // echoedSearchRetrieveRequest/sortKeys
1053        if (request.isVersion(SRUVersion.VERSION_1_1) &&
1054                (request.getSortKeys() != null)) {
1055            out.writeStartElement(ns.getResponseNS(), "sortKeys");
1056            out.writeCharacters(request.getSortKeys());
1057            out.writeEndElement(); // "sortKeys" element
1058        }
1059
1060        // echoedSearchRetrieveRequest/xsortKeys
1061
1062        // echoedSearchRetrieveRequest/stylesheet
1063        if (request.getStylesheet() != null) {
1064            out.writeStartElement(ns.getResponseNS(), "stylesheet");
1065            out.writeCharacters(request.getStylesheet());
1066            out.writeEndElement(); // "stylesheet" element
1067        }
1068
1069        // echoedSearchRetrieveRequest/renderedBy
1070        if (request.isVersion(SRUVersion.VERSION_2_0) && (request.getRenderBy() != null)) {
1071            out.writeStartElement(ns.getResponseNS(), "renderedBy");
1072            switch (request.getRenderBy()) {
1073            case SERVER:
1074                out.writeCharacters("server");
1075                break;
1076            case CLIENT:
1077                out.writeCharacters("client");
1078                break;
1079            }
1080            out.writeEndElement(); // "renderedBy" element
1081        }
1082
1083        // echoedSearchRetrieveRequest/extraRequestParameter
1084        // FIXME: NOT YET IMPLEMENTED
1085
1086        // echoedSearchRetrieveRequest/httpAccept
1087        if (request.isVersion(SRUVersion.VERSION_2_0) && (request.getRawHttpAccept() != null)) {
1088            out.writeStartElement(ns.getResponseNS(), "renderedBy");
1089            out.writeCharacters(request.getRawHttpAccept());
1090            out.writeEndElement(); // "renderedBy" element
1091        }
1092
1093        // echoedSearchRetrieveRequest/responseType
1094        if (request.isVersion(SRUVersion.VERSION_2_0) && (request.getResponeType() != null)) {
1095            out.writeStartElement(ns.getResponseNS(), "responseType");
1096            out.writeCharacters(request.getResponeType());
1097            out.writeEndElement(); // "responseType" element
1098        }
1099
1100        out.writeEndElement(); // "echoedSearchRetrieveRequest" element
1101    }
1102
1103
1104    private void writeVersion(SRUXMLStreamWriter out, SRUNamespaces ns,
1105            SRUVersion version) throws XMLStreamException {
1106        out.writeStartElement(ns.getResponseNS(), "version");
1107        switch (version) {
1108        case VERSION_1_1:
1109            out.writeCharacters("1.1");
1110            break;
1111        case VERSION_1_2:
1112            out.writeCharacters("1.2");
1113            break;
1114        case VERSION_2_0:
1115            out.writeCharacters("2.0");
1116            break;
1117        } // switch
1118        out.writeEndElement(); // "version" element
1119    }
1120
1121
1122    private void writeRecordPacking(SRUXMLStreamWriter out, SRUNamespaces ns,
1123            SRURecordXmlEscaping recordPacking) throws XMLStreamException {
1124        out.writeStartElement(ns.getResponseNS(), "recordPacking");
1125        switch (recordPacking) {
1126        case XML:
1127            out.writeCharacters("xml");
1128            break;
1129        case STRING:
1130            out.writeCharacters("string");
1131            break;
1132        } // switch
1133        out.writeEndElement(); // "recordPacking" element
1134    }
1135
1136
1137    private void writeLocalizedStrings(XMLStreamWriter writer,
1138            SRUNamespaces ns, String name, List<LocalizedString> list)
1139            throws XMLStreamException {
1140        if ((list != null) && !list.isEmpty()) {
1141            for (LocalizedString item : list) {
1142                writer.writeStartElement(ns.getExplainNS(), name);
1143                if (item.getLang() != null) {
1144                    writer.writeAttribute("lang", item.getLang());
1145                }
1146                if (item.isPrimary()) {
1147                    writer.writeAttribute("primary", "true");
1148                }
1149                writer.writeCharacters(item.getValue());
1150                writer.writeEndElement();
1151            }
1152        }
1153    }
1154
1155
1156    private SRUXMLStreamWriter createXMLStreamWriter(OutputStream out,
1157            SRURecordXmlEscaping recordPacking, boolean skipFlush, int indent)
1158            throws SRUException {
1159        try {
1160            if (skipFlush) {
1161                /*
1162                 * Add a FilterOutputStream to delay flush() as long as
1163                 * possible. Doing so, enabled us to send an appropriate SRU
1164                 * diagnostic in case an error occurs during the serialization
1165                 * of the response.
1166                 * Of course, if an error occurs when the Servlet response
1167                 * buffer already had been flushed, because it was to large,
1168                 * we cannot fail gracefully and we will produce ill-formed
1169                 * XML output.
1170                 */
1171                out = new FilterOutputStream(out) {
1172                    @Override
1173                    public void flush() throws IOException {
1174                    }
1175
1176
1177                    @Override
1178                    public void close() throws IOException {
1179                        super.flush();
1180                        super.close();
1181                    }
1182                };
1183            }
1184            return new SRUXMLStreamWriter(out, writerFactory,
1185                    recordPacking, indent);
1186        } catch (Exception e) {
1187            throw new SRUException(SRUConstants.SRU_GENERAL_SYSTEM_ERROR,
1188                    "Error creating output stream.", e);
1189
1190        }
1191    }
1192
1193
1194    private SRUNamespaces getNamespaces(SRUVersion version) {
1195        if (version == null) {
1196            throw new NullPointerException("version == null");
1197        }
1198        switch (version) {
1199        case VERSION_1_1:
1200            /* FALL-THROUGH */
1201        case VERSION_1_2:
1202            switch (config.getLegacyNamespaceMode()) {
1203            case LOC:
1204                return NAMESPACES_LEGACY_LOC;
1205            case OASIS:
1206                return NAMESPACES_1_2_OASIS;
1207            default:
1208                // FIXME: better exception?
1209                throw new IllegalAccessError("invalid legacy mode: " + version);
1210            } // switch
1211        case VERSION_2_0:
1212            return NAMESPACES_2_0;
1213        default:
1214            // FIXME: better exception?
1215            throw new IllegalAccessError("invalid version: " + version);
1216        }
1217    }
1218
1219
1220
1221
1222    private static final SRUNamespaces NAMESPACES_LEGACY_LOC = new SRUNamespaces() {
1223        private static final String SRU_NS =
1224                "http://www.loc.gov/zing/srw/";
1225        private static final String SRU_PREFIX =
1226                "sru";
1227        private static final String SRU_DIAGNOSIC_NS =
1228                "http://www.loc.gov/zing/srw/diagnostic/";
1229        private static final String SRU_DIAGNOSTIC_PREFIX =
1230                "diag";
1231        private static final String SRU_EXPLAIN_NS =
1232                "http://explain.z3950.org/dtd/2.0/";
1233        private static final String SRU_EXPLAIN_PREFIX =
1234                "zr";
1235        private static final String SRU_XCQL_NS =
1236                "http://www.loc.gov/zing/cql/xcql/";
1237
1238
1239        @Override
1240        public String getResponseNS() {
1241            return SRU_NS;
1242        }
1243
1244
1245        @Override
1246        public String getResponsePrefix() {
1247            return SRU_PREFIX;
1248        }
1249
1250
1251        @Override
1252        public String getScanNS() {
1253            return SRU_NS;
1254        }
1255
1256
1257        @Override
1258        public String getScanPrefix() {
1259            return SRU_PREFIX;
1260        }
1261
1262
1263        @Override
1264        public String getDiagnosticNS() {
1265            return SRU_DIAGNOSIC_NS;
1266        }
1267
1268
1269        @Override
1270        public String getDiagnosticPrefix() {
1271            return SRU_DIAGNOSTIC_PREFIX;
1272        }
1273
1274
1275        @Override
1276        public String getExplainNS() {
1277            return SRU_EXPLAIN_NS;
1278        }
1279
1280
1281        @Override
1282        public String getExplainPrefix() {
1283            return SRU_EXPLAIN_PREFIX;
1284        }
1285
1286
1287        @Override
1288        public String getXcqlNS() {
1289            return SRU_XCQL_NS;
1290        }
1291    };
1292
1293
1294    private static final SRUNamespaces NAMESPACES_1_2_OASIS = new SRUNamespaces() {
1295        private static final String SRU_RESPONSE_NS =
1296                "http://docs.oasis-open.org/ns/search-ws/sruResponse";
1297        private static final String SRU_RESPONSE_PREFIX =
1298                "sruResponse";
1299        private static final String SRU_SCAN_NS =
1300                "http://docs.oasis-open.org/ns/search-ws/scan";
1301        private static final String SRU_SCAN_PREFIX =
1302                "scan";
1303        private static final String SRU_DIAGNOSIC_NS =
1304                "http://docs.oasis-open.org/ns/search-ws/diagnostic";
1305        private static final String SRU_DIAGNOSTIC_PREFIX =
1306                "diag";
1307        private static final String SRU_EXPLAIN_NS =
1308                "http://explain.z3950.org/dtd/2.0/";
1309        private static final String SRU_EXPLAIN_PREFIX =
1310                "zr";
1311        private static final String SRU_XCQL_NS =
1312                "http://docs.oasis-open.org/ns/search-ws/xcql";
1313
1314
1315        @Override
1316        public String getResponseNS() {
1317            return SRU_RESPONSE_NS;
1318        }
1319
1320
1321        @Override
1322        public String getResponsePrefix() {
1323            return SRU_RESPONSE_PREFIX;
1324        }
1325
1326
1327        @Override
1328        public String getScanNS() {
1329            return SRU_SCAN_NS;
1330        }
1331
1332
1333        @Override
1334        public String getScanPrefix() {
1335            return SRU_SCAN_PREFIX;
1336        }
1337
1338
1339        @Override
1340        public String getDiagnosticNS() {
1341            return SRU_DIAGNOSIC_NS;
1342        }
1343
1344
1345        @Override
1346        public String getDiagnosticPrefix() {
1347            return SRU_DIAGNOSTIC_PREFIX;
1348        }
1349
1350
1351        @Override
1352        public String getExplainNS() {
1353            return SRU_EXPLAIN_NS;
1354        }
1355
1356
1357        @Override
1358        public String getExplainPrefix() {
1359            return SRU_EXPLAIN_PREFIX;
1360        }
1361
1362
1363        @Override
1364        public String getXcqlNS() {
1365            return SRU_XCQL_NS;
1366        }
1367    };
1368
1369
1370    private static final SRUNamespaces NAMESPACES_2_0 = new SRUNamespaces() {
1371        private static final String SRU_RESPONSE_NS =
1372                "http://docs.oasis-open.org/ns/search-ws/sruResponse";
1373        private static final String SRU_RESPONSE_PREFIX =
1374                "sruResponse";
1375        private static final String SRU_SCAN_NS =
1376                "http://docs.oasis-open.org/ns/search-ws/scan";
1377        private static final String SRU_SCAN_PREFIX =
1378                "scan";
1379        private static final String SRU_DIAGNOSIC_NS =
1380                "http://docs.oasis-open.org/ns/search-ws/diagnostic";
1381        private static final String SRU_DIAGNOSTIC_PREFIX =
1382                "diag";
1383        private static final String SRU_EXPLAIN_NS =
1384                "http://explain.z3950.org/dtd/2.0/";
1385        private static final String SRU_EXPLAIN_PREFIX =
1386                "zr";
1387        private static final String SRU_XCQL_NS =
1388                "http://docs.oasis-open.org/ns/search-ws/xcql";
1389
1390
1391        @Override
1392        public String getResponseNS() {
1393            return SRU_RESPONSE_NS;
1394        }
1395
1396
1397        @Override
1398        public String getResponsePrefix() {
1399            return SRU_RESPONSE_PREFIX;
1400        }
1401
1402
1403        @Override
1404        public String getScanNS() {
1405            return SRU_SCAN_NS;
1406        }
1407
1408
1409        @Override
1410        public String getScanPrefix() {
1411            return SRU_SCAN_PREFIX;
1412        }
1413
1414
1415        @Override
1416        public String getDiagnosticNS() {
1417            return SRU_DIAGNOSIC_NS;
1418        }
1419
1420
1421        @Override
1422        public String getDiagnosticPrefix() {
1423            return SRU_DIAGNOSTIC_PREFIX;
1424        }
1425
1426
1427        @Override
1428        public String getExplainNS() {
1429            return SRU_EXPLAIN_NS;
1430        }
1431
1432
1433        @Override
1434        public String getExplainPrefix() {
1435            return SRU_EXPLAIN_PREFIX;
1436        }
1437
1438
1439        @Override
1440        public String getXcqlNS() {
1441            return SRU_XCQL_NS;
1442        }
1443    };
1444
1445} // class SRUServer
Note: See TracBrowser for help on using the repository browser.