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

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