Changeset 1911


Ignore:
Timestamp:
04/26/12 13:25:34 (12 years ago)
Author:
oschonef
Message:
  • first round of major refactoring to simply the provider
    • result sets converted to iterator/cursor-mode (better streaming support)
    • delegate serializing of records to Result and ResultList?
    • prepare getting rid of over-engineered and complicated MetadataFormats? classes (not done, yet)

HEADS UP: breaks existing API

Location:
OAIProvider/trunk/src/main/java/eu/clarin/oai/provider
Files:
3 deleted
21 edited

Legend:

Unmodified
Added
Removed
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/MetadataFormat.java

    r1910 r1911  
    11package eu.clarin.oai.provider;
    22
    3 import javax.xml.stream.XMLStreamException;
    4 import javax.xml.stream.XMLStreamWriter;
    53
    64
     
    4240    public String getSchemaLocation();
    4341
    44     /**
    45      * Check, if objects of a given class can be written by this metadata
    46      * format.
    47      *
    48      * @param clazz
    49      *            the class of the object
    50      * @return <code>true</code> if objects can be disseminated,
    51      *         <code>false</code> otherwise
    52      */
    53     public boolean canWriteClass(Class<?> clazz);
    54 
    55     /**
    56      * Transform an object into the metadata format and write it to the output
    57      * stream. Used by the <em>GetRecord</em> and <em>ListRecords</em> verbs.
    58      *
    59      * @param stream
    60      *            the output stream
    61      * @param item
    62      *            the object, which is to be written
    63      * @throws OAIException
    64      *             if an error occurs
    65      */
    66     public void writeObject(XMLStreamWriter stream, Object item)
    67             throws XMLStreamException, OAIException;
    68 
    6942} // interface MetadataFormat
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/Record.java

    r1910 r1911  
    33import java.util.Date;
    44import java.util.List;
     5
     6import javax.xml.stream.XMLStreamException;
     7import javax.xml.stream.XMLStreamWriter;
    58
    69/**
     
    1013 */
    1114public interface Record {
    12 
    13     /**
    14      * Set the local Id of the record. The returned object must be compatible
    15      * with repository's implementation of <code>parseLocalId</code> and
    16      * <code>unparseLocalId</code>.
    17      *
    18      * @param localId
    19      *            the local Id object of the record
    20      *
    21      * @see Repository#parseLocalId(String)
    22      * @see Repository#unparseLocalId(Object)
    23      */
    24     public void setLocalId(Object localId);
    25 
    2615    /**
    2716     * Get the localId of the record. The returned object must be compatible
     
    3625   
    3726    /**
    38      * Set the last changed timestamp of the record.
    39      *
    40      * @param datestamp
    41      *            the last changed timestamp of the record
    42      * @return the last changed timestamp of the record
    43      * @see Repository#getGranularity()
    44      */
    45     public void setDatestamp(Date datestamp);
    46 
    47     /**
    4827     * Get the last changed timestamp of the item.
    4928     *
     
    5231     */
    5332    public Date getDatestamp();
    54 
    55     /**
    56      * set deletion state of the record.
    57      *
    58      * @param deleted
    59      *           <code>true</code> if the record is deleted,
    60      *           <code>false</code> otherwise
    61      * @see Repository#getDeletedNotion()           
    62      */
    63     public void setDeleted(boolean deleted);
    6433
    6534    /**
     
    7241    public boolean isDeleted();
    7342
    74    
    75     /**
    76      * Set the sets to which this record belongs to.
    77      *
    78      * @param setSpecs
    79      *           a list of sets to which this record belongs to.
    80      *           May be set to <code>null</code> if the record does not
    81      *           belong to any set, but explicitly settings this is
    82      *           not necessary
    83      */
    84     public void setSetSpecs(List<String> setSpecs);
    85 
    86     /**
    87      * Add a set to the sets to which this record belongs
    88      *
    89      * @param setSpec
    90      *           a name of a set to which the record belongs
    91      */
    92     public void addSetSpec(String setSpec);
    93 
    9443    /**
    9544     * Get the sets to which this record belongs to.
     
    10049     */
    10150    public List<String> getSetSpecs();
    102 
    103     /**
    104      * Set the actual item as the native object. This object is passed to the
    105      * <code>writeObject</code> method of <code>MetadataFormat</code>. The
    106      * item <em>must</em> be set, if a full record is requested.
    107      *
    108      * @param item
    109      *           the object as a native object or <code>null</code> if the
    110      *           record only contains header information
    111      * @see MetadataFormat#writeObject(eu.clarin.cmdi.oai.provider.OAIOutputStream,
    112      *      Object)
    113      */
    114     public void setItem(Object item);
    115 
    116     /**
    117      * Get the actual item as the native object. This object is passed to the
    118      * <code>writeObject</code> method of <code>MetadataFormat</code>.
    119      *
    120      * @return the object as a native object or <code>null</code> if the record
    121      *         only contains header information
    122      * @see MetadataFormat#writeObject(eu.clarin.cmdi.oai.provider.OAIOutputStream,
    123      *      Object)
    124      */
    125     public Object getItem();
    126 
     51   
     52    public void writeRecord(XMLStreamWriter writer) throws XMLStreamException;
    12753} // interface Record
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/RecordList.java

    r1910 r1911  
    11package eu.clarin.oai.provider;
    22
     3import java.util.Date;
     4import java.util.List;
     5
     6import javax.xml.stream.XMLStreamException;
     7import javax.xml.stream.XMLStreamWriter;
    38
    49/**
     
    611 * <em>ListRecords</em> and <em>ListIdentifiers</em> verb. This can also be a
    712 * partial list if the repository wants do flow-control.
     13 *
     14 * A <code>RecordList</code> object maintains a cursor pointing to its
     15 * current record. Initially the cursor is positioned before the first row.
     16 * The next method moves the cursor to the next row, and because it returns
     17 * <code>false</code> when there are no more records in the
     18 * <code>RecordList</code> object, it can be used in a while loop to iterate through the
     19 * result set.
    820 */
    921public interface RecordList {
    1022
    1123    /**
    12      * Release resources as this record list instance is about to be discared.
    13      */
    14     public void close();
    15 
    16     /**
    17      * Returns <code>true</code> if the list has more records. (In other
    18      * words, returns <code>true</code> if <code>nextRecord</code> would
    19      * return an element rather than throwing an exception.)
    20      * 
    21      *  @return <code>true</code> if the list has more records
    22      */
    23     public boolean hasNextRecord();
    24 
    25     /**
    26      * Gets the next record in the list. RecordList implementations must set
    27      * the appropriate values to the passed Record instance.
    28      *
    29      * @param record
    30      *            a Record instance to gather the record data
    31      * @see Record
    32      * @throws NoSuchElementException - iteration has no more results.
    33      */
    34     public void nextRecord(Record record);
    35 
    36     /**
    37      * Return <code>true</code>, if no more partial result lists are
    38      * available for this request. In other words, this method should return
    39      * <code>true</code> either  if this list contains all appropriate records
    40      * for the request or if this result list is the last partial list for the
    41      * request. This is needed for flow-control.
    42      *
    43      * @return <code>true</code> if no more results are available in
    44      *         subsequent requests, <code>false</code> otherwise
    45      */
    46     public boolean isListComplete();
    47 
    48     /**
    4924     * Get the size of the complete result list.
    5025     *
    51      * @return the size of the complete result list or <code>-1</code>, if
    52      *         the repository cannot calculate the total size.
     26     * @return the size of the complete result list or <code>-1</code>, if the
     27     *         repository cannot calculate the total size.
    5328     * @see #getSize()
    5429     */
    5530    public int getTotalSize();
    5631
     32
     33    /**
     34     * Return <code>true</code>, if no more partial result lists are available
     35     * for this request. In other words, this method should return
     36     * <code>true</code> either if this list contains all appropriate records
     37     * for the request or if this result list is the last partial list for the
     38     * request. This is needed for flow-control.
     39     *
     40     * @return <code>true</code> if no more results are available in subsequent
     41     *         requests, <code>false</code> otherwise
     42     */
     43    public boolean isListComplete();
     44
     45
     46    /**
     47     * Release resources as this record list instance is about to be discarded.
     48     */
     49    public void close();
     50
     51   
     52    /**
     53     * Returns <code>true</code> if the list has more records. (In other words,
     54     * returns <code>true</code> if <code>nextRecord</code> would return an
     55     * element rather than throwing an exception.)
     56     *
     57     * @return <code>true</code> if the list has more records
     58     */
     59    public boolean next();
     60
     61   
     62    /**
     63     * Get the localId of the record. The returned object must be compatible
     64     * with repository's implementation of <code>parseLocalId</code> and
     65     * <code>unparseLocalId</code>.
     66     *
     67     * @returns the local Id object of the record
     68     */
     69    public Object getLocalId();
     70
     71    /**
     72     * Get the last changed timestamp of the item.
     73     *
     74     * @return the last changed timestamp of the item
     75     */
     76    public Date getDatestamp();
     77
     78    /**
     79     * set deletion state of the record.
     80     *
     81     * @return <code>true</code> of the record is deleted, <code>false</code>
     82     *         otherwise
     83     * @see Repository#getDeletedNotion()           
     84     */
     85    public boolean isDeleted();
     86
     87    /**
     88     * Get the sets to which this record belongs to.
     89     *
     90     * @return the list of sets to which the records belongs to or
     91     *         <code>null</code> if it does not belong to any sets or the
     92     *         repository does not support set
     93     */
     94    public List<String> getSetSpecs();
     95
     96   
     97    public void writeRecord(XMLStreamWriter writer) throws XMLStreamException;
     98   
     99    // /**
     100    // * Gets the next record in the list. RecordList implementations must set
     101    // * the appropriate values to the passed Record instance.
     102    // *
     103    // * @param record
     104    // * a Record instance to gather the record data
     105    // * @see Record
     106    // * @throws NoSuchElementException - iteration has no more results.
     107    // */
     108    // public void nextRecord(Record record);
     109    //
     110    // /**
     111    // * Return <code>true</code>, if no more partial result lists are
     112    // * available for this request. In other words, this method should return
     113    // * <code>true</code> either if this list contains all appropriate records
     114    // * for the request or if this result list is the last partial list for the
     115    // * request. This is needed for flow-control.
     116    // *
     117    // * @return <code>true</code> if no more results are available in
     118    // * subsequent requests, <code>false</code> otherwise
     119    // */
     120    // public boolean isListComplete();
     121    //
     122
    57123} // class RecordList
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/Repository.java

    r1910 r1911  
    185185     * Used for <em>GetRecord</em> and <em>ListMetadataFormat<em> verbs.
    186186     *
    187      * @param record
    188      *            the record object for storing the appropriate values
    189187     * @param localId
    190188     *            the local id object of the item, which is requested by the
    191189     *            provider
    192      * @param headerOnly
    193      *            <code>true</code> if the provider only needs enough
    194      *            information from the repository to generate item header
    195      *            information and not the complete item
    196      * @returns <code>true</code> if the requested record was found,
    197      *          <code>false<code> otherwise
     190     * @returns the record or <code>null</code>
    198191     * @throws OAIException if an error occurred
    199192     * @see #parseLocalId(String)
    200193     */
    201     public boolean getRecord(Record record, Object localId, boolean headerOnly)
    202             throws OAIException;
     194    public Record getRecord(Object localId) throws OAIException;
     195
     196    /**
     197     * Fetch an item from the repository matching a given id. The repository is
     198     * required to set the appropriate values in the supplied record object.
     199     * Used for <em>GetRecord</em> and <em>ListMetadataFormat<em> verbs.
     200     *
     201     * @param localId
     202     *            the local id object of the item, which is requested by the
     203     *            provider
     204     * @returns record the record or <code>null</code>
     205     * @throws OAIException if an error occurred
     206     * @see #parseLocalId(String)
     207     */
     208    public Record getRecordHeader(Object localId) throws OAIException;
    203209
    204210    /**
     
    231237     *            maximum requested result list size; a repository should
    232238     *            return results only up to this value
    233      * @param headerOnly
    234      *            <code>true</code> if the provider only needs enough
    235      *            information from the repository to generate item header
    236      *            information and not the complete item
    237239     * @returns the list of matched items or <code>null</code> if none matched
    238240     *          the criteria
     
    241243     */
    242244    public RecordList getRecords(String prefix, Date from, Date until,
    243             String set, int offset, int limit, boolean headerOnly)
    244             throws OAIException;
     245            String set, int offset, int limit) throws OAIException;
     246
     247    /**
     248     * Fetch items from the repository matching the given criteria. Used for
     249     * <em>ListRecords</em> and <em>ListIdentifiers</em> verbs.
     250     *
     251     * @param prefix
     252     *            requested metadata format (e.g "oai_dc")
     253     * @param from
     254     *            optional lower date, may be <code>null</code>
     255     * @param until
     256     *            optional upper date, may be <code>null</code>
     257     * @param set
     258     *            optional set specification, may be <code>null</code>
     259     * @param offset
     260     *            start position if resumption request, is
     261     *            <code>-1<code> for the first request
     262     * @param limit
     263     *            maximum requested result list size; a repository should
     264     *            return results only up to this value
     265     * @returns the list of matched items or <code>null</code> if none matched
     266     *          the criteria
     267     * @throws OAIException if an error occurred
     268     * @see RecordList
     269     */
     270    public RecordList getRecordHeaders(String prefix, Date from, Date until,
     271            String set, int offset, int limit) throws OAIException;
    245272
    246273} // interface Repository
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/ext/OAIOutputStream.java

    r1910 r1911  
    11package eu.clarin.oai.provider.ext;
    22
     3import java.io.IOException;
    34import java.util.Date;
    45import java.util.List;
    56
    67import javax.xml.XMLConstants;
     8import javax.xml.stream.XMLStreamException;
    79import javax.xml.stream.XMLStreamWriter;
    810
    9 import eu.clarin.oai.provider.MetadataFormat;
    10 import eu.clarin.oai.provider.OAIException;
    1111import eu.clarin.oai.provider.Record;
    1212
     
    4545    } // class NamespaceDecl
    4646
    47     public void close() throws OAIException;
     47    public void close() throws IOException, XMLStreamException;
    4848
    49     public void flush() throws OAIException;
     49    public void flush() throws XMLStreamException;
    5050
    51     public XMLStreamWriter getXMLStreamWriter() throws OAIException;
     51    public XMLStreamWriter getXMLStreamWriter() throws XMLStreamException;
    5252
    53     public void writeStartElement(String localName) throws OAIException;
     53    public void writeStartElement(String localName) throws XMLStreamException;
    5454
    5555    public void writeStartElement(String namespaceURI, String localName)
    56             throws OAIException;
     56            throws XMLStreamException;
    5757
    5858    public void writeStartElement(String namespaceURI, String localName,
    59             List<NamespaceDecl> decls) throws OAIException;
     59            List<NamespaceDecl> decls) throws XMLStreamException;
    6060
    61     public void writeEndElement() throws OAIException;
     61    public void writeEndElement() throws XMLStreamException;
    6262
    6363    public void writeAttribute(String localName, String value)
    64             throws OAIException;
     64            throws XMLStreamException;
    6565
    6666    public void writeAttribute(String namespaceURI, String localName,
    67             String value) throws OAIException;
     67            String value) throws XMLStreamException;
    6868
    69     public void writeCharacters(String text) throws OAIException;
     69    public void writeCharacters(String text) throws XMLStreamException;
    7070
    71     public void writeDate(Date date) throws OAIException;
     71    public void writeDate(Date date) throws XMLStreamException;
    7272
    73     public void writeResumptionToken(ResumptionToken token) throws OAIException;
     73    public void writeResumptionToken(ResumptionToken token)
     74            throws XMLStreamException;
    7475
    75     public void writeRecordHeader(Record record) throws OAIException;
     76    public void writeRecordHeader(Record record) throws XMLStreamException;
    7677   
    77     public void writeRecord(Record record, MetadataFormat format)
    78             throws OAIException;
     78    public void writeRecord(Record record) throws XMLStreamException;
    7979
    8080} // interface OAIOutputStream
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/ext/RepositoryAdapter.java

    r1910 r1911  
    66import eu.clarin.oai.provider.MetadataFormat;
    77import eu.clarin.oai.provider.OAIException;
    8 import eu.clarin.oai.provider.OAIRepositoryImplementationError;
    98import eu.clarin.oai.provider.Record;
    109import eu.clarin.oai.provider.RecordList;
     
    4948    public Object parseLocalId(String unparsedLocalId);
    5049
    51     public boolean getRecord(Record record, Object localId,
    52             boolean headerOnly) throws OAIException;
     50    public Record getRecord(Object localId) throws OAIException;
     51
     52    public Record getRecordHeader(Object localId) throws OAIException;
    5353
    5454    public RecordList getRecords(String prefix, Date from, Date until,
    55             String set, int offset, boolean headerOnly) throws OAIException;
     55            String set, int offset) throws OAIException;
    5656
    57     public void checkRecord(Record record, boolean headerOnly)
    58             throws OAIRepositoryImplementationError;
     57    public RecordList getRecordHeaders(String prefix, Date from, Date until,
     58            String set, int offset) throws OAIException;
    5959
    6060    public ResumptionToken createResumptionToken();
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/ext/Verb.java

    r1910 r1911  
    11package eu.clarin.oai.provider.ext;
     2
     3import java.io.IOException;
     4
     5import javax.xml.stream.XMLStreamException;
    26
    37import org.slf4j.Logger;
     
    1317    public abstract Argument[] getArguments();
    1418
    15     public abstract void process(VerbContext ctx) throws OAIException;
     19    public abstract void process(VerbContext ctx) throws IOException,
     20            XMLStreamException, OAIException;
    1621
    1722    public final boolean supportsArgument(String name) {
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/ext/VerbContext.java

    r1910 r1911  
    11package eu.clarin.oai.provider.ext;
    22
     3import java.io.IOException;
    34import java.util.List;
    45import java.util.Map;
    56
    6 import eu.clarin.oai.provider.OAIException;
     7import javax.xml.stream.XMLStreamException;
    78
    89public interface VerbContext {
     
    3334    public List<Error> getErrors();
    3435
    35     public OAIOutputStream getOutputStream() throws OAIException;
     36    public OAIOutputStream getOutputStream() throws IOException,
     37            XMLStreamException;
    3638
    3739} // interface VerbContext
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/DublinCoreMetadataFormat.java

    r1910 r1911  
    11package eu.clarin.oai.provider.impl;
    22
    3 import java.util.Date;
    4 import java.util.List;
    53import java.util.Set;
    6 import java.util.TimeZone;
    7 
    8 import javax.xml.XMLConstants;
    9 import javax.xml.stream.XMLStreamException;
    10 import javax.xml.stream.XMLStreamWriter;
    11 
    12 import org.apache.commons.lang.time.FastDateFormat;
    134
    145import eu.clarin.oai.provider.DublinCoreConverter;
    156import eu.clarin.oai.provider.MetadataFormat;
    16 import eu.clarin.oai.provider.OAIException;
    177
    188final class DublinCoreMetadataFormat implements MetadataFormat {
    19     private static final String SCHEMA_LOCATION =
    20         MetadataConstants.NS_OAI_DC + " " +
    21         MetadataConstants.NS_OAI_DC_SCHEMA_LOCATION;
    22     private final static FastDateFormat fmt =
    23         FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",
    24                                    TimeZone.getTimeZone("UTC"));
    25     private final Set<DublinCoreConverter> converters;
     9//    private static final String SCHEMA_LOCATION =
     10//        MetadataConstants.NS_OAI_DC + " " +
     11//        MetadataConstants.NS_OAI_DC_SCHEMA_LOCATION;
     12//    private final static FastDateFormat fmt =
     13//        FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",
     14//                                   TimeZone.getTimeZone("UTC"));
     15//    private final Set<DublinCoreConverter> converters;
    2616
    2717    DublinCoreMetadataFormat(Set<DublinCoreConverter> converters) {
    28         this.converters = converters;
     18//        this.converters = converters;
    2919    }
    3020
     
    4535    }
    4636
    47     @Override
    48     public boolean canWriteClass(Class<?> clazz) {
    49         return getConverter(clazz) != null;
    50     }
    51    
    52     @Override
    53     public final void writeObject(XMLStreamWriter stream, Object item)
    54             throws XMLStreamException, OAIException {
    55         final DublinCoreConverter converter = getConverter(item.getClass());
    56        
    57         stream.setPrefix("oai_dc", MetadataConstants.NS_OAI_DC);
    58         stream.setPrefix("dc", MetadataConstants.NS_DC);
    59         stream.writeStartElement(MetadataConstants.NS_OAI_DC, "dc");
    60         stream.writeNamespace("oai_dc", MetadataConstants.NS_OAI_DC);
    61         stream.writeNamespace("dc", MetadataConstants.NS_DC);
    62         stream.writeNamespace("xsi",
    63                 XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
    64         stream.writeAttribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
    65                 "schemaLocation", SCHEMA_LOCATION);
    66 
    67         // dc:title
    68         writeElement(stream, "title", converter.getTitles(item));
    69 
    70         // dc:creator
    71         writeElement(stream, "creator", converter.getCreators(item));
    72 
    73         // dc:subject
    74         writeElement(stream, "subject", converter.getSubjects(item));
    75 
    76         // dc:description
    77         writeElement(stream, "description", converter.getDescriptions(item));
    78 
    79         // dc:publisher
    80         writeElement(stream, "publisher", converter.getPublishers(item));
    81 
    82         // dc:contributor
    83         writeElement(stream, "contributor", converter.getContributors(item));
    84 
    85         // dc:date
    86         List<Date> dates = converter.getDates(item);
    87         if ((dates != null) && !dates.isEmpty()) {
    88             for (Date date : dates) {
    89                 stream.writeStartElement(MetadataConstants.NS_DC, "date");
    90                 stream.writeCharacters(fmt.format(date));
    91                 stream.writeEndElement(); // close element
    92             }
    93         }
    94 
    95         // dc:type
    96         writeElement(stream, "type", converter.getTypes(item));
    97 
    98         // dc:format
    99         writeElement(stream, "format", converter.getFormats(item));
    100 
    101         // dc:identifier
    102         writeElement(stream, "identifier", converter.getIdentifiers(item));
    103 
    104         // dc:source
    105         writeElement(stream, "source", converter.getSources(item));
    106 
    107         // dc:language
    108         writeElement(stream, "language", converter.getLanguages(item));
    109 
    110         // dc:relation
    111         writeElement(stream, "relation", converter.getRelations(item));
    112 
    113         // dc:coverage
    114         writeElement(stream, "coverage", converter.getCoverages(item));
    115 
    116         // dc:rights
    117         writeElement(stream, "rights", converter.getRights(item));
    118 
    119         stream.writeEndElement(); // "oai_dc:dc" element
    120     }
    121 
    122     private void writeElement(XMLStreamWriter stream, String element,
    123             List<String> values) throws XMLStreamException {
    124         if ((values != null) && !values.isEmpty()) {
    125             for (String title : values) {
    126                 stream.writeStartElement(MetadataConstants.NS_DC, element);
    127                 stream.writeCharacters(title);
    128                 stream.writeEndElement(); // close element
    129             }
    130         }
    131     }
    132 
    133     private DublinCoreConverter getConverter(Class<?> clazz) {
    134         for (DublinCoreConverter conveter : converters) {
    135             if (conveter.canProcessResource(clazz)) {
    136                 return conveter;
    137             }
    138         }
    139         return null;
    140     }
     37//    @Override
     38//    public boolean canWriteClass(Class<?> clazz) {
     39//        return getConverter(clazz) != null;
     40//    }
     41//   
     42//    @Override
     43//    public final void writeObject(XMLStreamWriter stream, Object item)
     44//            throws XMLStreamException, OAIException {
     45//        final DublinCoreConverter converter = getConverter(item.getClass());
     46//       
     47//        stream.setPrefix("oai_dc", MetadataConstants.NS_OAI_DC);
     48//        stream.setPrefix("dc", MetadataConstants.NS_DC);
     49//        stream.writeStartElement(MetadataConstants.NS_OAI_DC, "dc");
     50//        stream.writeNamespace("oai_dc", MetadataConstants.NS_OAI_DC);
     51//        stream.writeNamespace("dc", MetadataConstants.NS_DC);
     52//        stream.writeNamespace("xsi",
     53//                XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
     54//        stream.writeAttribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
     55//                "schemaLocation", SCHEMA_LOCATION);
     56//
     57//        // dc:title
     58//        writeElement(stream, "title", converter.getTitles(item));
     59//
     60//        // dc:creator
     61//        writeElement(stream, "creator", converter.getCreators(item));
     62//
     63//        // dc:subject
     64//        writeElement(stream, "subject", converter.getSubjects(item));
     65//
     66//        // dc:description
     67//        writeElement(stream, "description", converter.getDescriptions(item));
     68//
     69//        // dc:publisher
     70//        writeElement(stream, "publisher", converter.getPublishers(item));
     71//
     72//        // dc:contributor
     73//        writeElement(stream, "contributor", converter.getContributors(item));
     74//
     75//        // dc:date
     76//        List<Date> dates = converter.getDates(item);
     77//        if ((dates != null) && !dates.isEmpty()) {
     78//            for (Date date : dates) {
     79//                stream.writeStartElement(MetadataConstants.NS_DC, "date");
     80//                stream.writeCharacters(fmt.format(date));
     81//                stream.writeEndElement(); // close element
     82//            }
     83//        }
     84//
     85//        // dc:type
     86//        writeElement(stream, "type", converter.getTypes(item));
     87//
     88//        // dc:format
     89//        writeElement(stream, "format", converter.getFormats(item));
     90//
     91//        // dc:identifier
     92//        writeElement(stream, "identifier", converter.getIdentifiers(item));
     93//
     94//        // dc:source
     95//        writeElement(stream, "source", converter.getSources(item));
     96//
     97//        // dc:language
     98//        writeElement(stream, "language", converter.getLanguages(item));
     99//
     100//        // dc:relation
     101//        writeElement(stream, "relation", converter.getRelations(item));
     102//
     103//        // dc:coverage
     104//        writeElement(stream, "coverage", converter.getCoverages(item));
     105//
     106//        // dc:rights
     107//        writeElement(stream, "rights", converter.getRights(item));
     108//
     109//        stream.writeEndElement(); // "oai_dc:dc" element
     110//    }
     111//
     112//    private void writeElement(XMLStreamWriter stream, String element,
     113//            List<String> values) throws XMLStreamException {
     114//        if ((values != null) && !values.isEmpty()) {
     115//            for (String title : values) {
     116//                stream.writeStartElement(MetadataConstants.NS_DC, element);
     117//                stream.writeCharacters(title);
     118//                stream.writeEndElement(); // close element
     119//            }
     120//        }
     121//    }
     122//
     123//    private DublinCoreConverter getConverter(Class<?> clazz) {
     124//        for (DublinCoreConverter conveter : converters) {
     125//            if (conveter.canProcessResource(clazz)) {
     126//                return conveter;
     127//            }
     128//        }
     129//        return null;
     130//    }
    141131
    142132} // class DublinCoreMetadataFormat
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/IPFilter.java

    r1910 r1911  
    258258
    259259    public boolean accept(String address) {
     260        if (address == null) {
     261            return false;
     262        }
     263
    260264        int addr = Subnet.parseQuattedDot(address);
    261265        if (whitelist.isInList(addr)) {
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/OAIOutputStreamImpl.java

    r1910 r1911  
    1616import org.apache.commons.lang.time.FastDateFormat;
    1717
    18 import eu.clarin.oai.provider.MetadataFormat;
    19 import eu.clarin.oai.provider.OAIException;
    2018import eu.clarin.oai.provider.Record;
    2119import eu.clarin.oai.provider.Repository;
     
    104102    private final XMLStreamWriter writer;
    105103
    106     OAIOutputStreamImpl(VerbContext ctx, OutputStream out) throws OAIException {
    107         try {
    108             this.repository = ctx.getRepository();
    109             this.stream = new FlushSkipOutputStream(out, 8192);
    110             synchronized (writerFactory) {
    111                 writer = writerFactory.createXMLStreamWriter(stream, "utf-8");
    112             } // synchronized (writerFactory)
    113             writer.writeStartDocument("utf-8", "1.0");
    114 
    115             StringBuilder data = new StringBuilder();
    116             data.append("type=\"text/xsl\" href=\"");
    117             data.append(ctx.getContextPath());
    118             data.append("/oai2.xsl\"");
    119             writer.writeProcessingInstruction("xml-stylesheet",
    120                     data.toString());
    121 
    122             writer.setDefaultNamespace(NS_OAI);
    123             writer.writeStartElement("OAI-PMH");
    124             writer.writeDefaultNamespace(NS_OAI);
    125             writer.writeNamespace("xsi",
    126                     XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
    127             writer.writeAttribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
    128                     "schemaLocation", SCHEMA_LOCATION);
    129 
    130             FastDateFormat fmt =
    131                 getDateFormat(Repository.Granularity.SECONDS);
    132             writer.writeStartElement("responseDate");
    133             writer.writeCharacters(fmt.format(System.currentTimeMillis()));
    134             writer.writeEndElement(); // responseDate element
    135 
    136             writer.writeStartElement("request");
    137             if (ctx.getVerb() != null) {
    138                 writer.writeAttribute("verb", ctx.getVerb());
    139                 Map<String, String> args = ctx.getUnparsedArguments();
    140                 for (Map.Entry<String, String> item : args.entrySet()) {
    141                     writer.writeAttribute(item.getKey(), item.getValue());
     104    OAIOutputStreamImpl(VerbContext ctx, OutputStream out)
     105            throws XMLStreamException {
     106        this.repository = ctx.getRepository();
     107        this.stream = new FlushSkipOutputStream(out, 8192);
     108        writer = writerFactory.createXMLStreamWriter(stream, "utf-8");
     109        writer.writeStartDocument("utf-8", "1.0");
     110
     111        // FIXME: make this a configuration option
     112        StringBuilder data = new StringBuilder();
     113        data.append("type=\"text/xsl\" href=\"");
     114        data.append(ctx.getContextPath());
     115        data.append("/oai2.xsl\"");
     116        writer.writeProcessingInstruction("xml-stylesheet", data.toString());
     117
     118        writer.setDefaultNamespace(NS_OAI);
     119        writer.writeStartElement("OAI-PMH");
     120        writer.writeDefaultNamespace(NS_OAI);
     121        writer.writeNamespace("xsi",
     122                XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
     123        writer.writeAttribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
     124                "schemaLocation", SCHEMA_LOCATION);
     125
     126        FastDateFormat fmt = getDateFormat(Repository.Granularity.SECONDS);
     127        writer.writeStartElement("responseDate");
     128        writer.writeCharacters(fmt.format(System.currentTimeMillis()));
     129        writer.writeEndElement(); // responseDate element
     130
     131        writer.writeStartElement("request");
     132        if (ctx.getVerb() != null) {
     133            writer.writeAttribute("verb", ctx.getVerb());
     134            Map<String, String> args = ctx.getUnparsedArguments();
     135            for (Map.Entry<String, String> item : args.entrySet()) {
     136                writer.writeAttribute(item.getKey(), item.getValue());
     137            }
     138        }
     139        writer.writeCharacters(ctx.getRequestURI());
     140        writer.writeEndElement(); // request element
     141    }
     142
     143    @Override
     144    public void close() throws IOException, XMLStreamException {
     145        writer.writeEndElement(); // OAI-PMH (root) element
     146        writer.writeEndDocument();
     147        writer.flush();
     148        writer.close();
     149        // explicitly close output stream, as XMLStreamWriter does not!
     150        stream.close();
     151    }
     152
     153    @Override
     154    public void flush() throws XMLStreamException {
     155        writer.flush();
     156    }
     157
     158    @Override
     159    public XMLStreamWriter getXMLStreamWriter() throws XMLStreamException {
     160        return writer;
     161    }
     162
     163    @Override
     164    public void writeStartElement(String localName) throws XMLStreamException {
     165        writer.writeStartElement(localName);
     166    }
     167
     168    @Override
     169    public void writeStartElement(String namespaceURI, String localName)
     170            throws XMLStreamException {
     171        writer.writeStartElement(namespaceURI, localName);
     172    }
     173
     174    @Override
     175    public void writeStartElement(String namespaceURI, String localName,
     176            List<NamespaceDecl> decls) throws XMLStreamException {
     177        for (NamespaceDecl decl : decls) {
     178            writer.setPrefix(decl.getPrefix(), decl.getNamespaceURI());
     179        }
     180        writer.writeStartElement(namespaceURI, localName);
     181        boolean schemaDeclWritten = false;
     182        for (NamespaceDecl decl : decls) {
     183            writer.writeNamespace(decl.getPrefix(), decl.getNamespaceURI());
     184            if (decl.hasSchemaLocation()) {
     185                /*
     186                 * From an XML point of view, the XSI-namespace is still in
     187                 * scope and this is not needed, but all other providers show
     188                 * this behavior.
     189                 */
     190                if (!schemaDeclWritten) {
     191                    writer.writeNamespace("xsi",
     192                            XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
     193                    schemaDeclWritten = true;
    142194                }
    143             }
    144             writer.writeCharacters(ctx.getRequestURI());
    145             writer.writeEndElement(); // request element
    146         } catch (XMLStreamException e) {
    147             throw new OAIException("error while serializing response", e);
    148         }
    149     }
    150 
    151     @Override
    152     public void close() throws OAIException {
    153         try {
    154             writer.writeEndElement(); // OAI-PMH (root) element
    155             writer.writeEndDocument();
    156             writer.flush();
    157             writer.close();
    158             // explicitly close output stream, as XMLStreamWriter does not!
    159             stream.close();
    160         } catch (Exception e) {
    161             throw new OAIException("error while serializing response", e);
    162         }
    163     }
    164 
    165     @Override
    166     public void flush() throws OAIException {
    167         try {
    168             writer.flush();
    169         } catch (XMLStreamException e) {
    170             throw new OAIException("error while serializing response", e);
    171         }
    172     }
    173 
    174     @Override
    175     public XMLStreamWriter getXMLStreamWriter() throws OAIException {
    176         return writer;
    177     }
    178 
    179     @Override
    180     public void writeStartElement(String localName) throws OAIException {
    181         try {
    182             writer.writeStartElement(localName);
    183         } catch (XMLStreamException e) {
    184             throw new OAIException("error while serializing response", e);
    185         }
    186     }
    187 
    188     @Override
    189     public void writeStartElement(String namespaceURI, String localName)
    190             throws OAIException {
    191         try {
    192             writer.writeStartElement(namespaceURI, localName);
    193         } catch (XMLStreamException e) {
    194             throw new OAIException("error while serializing response", e);
    195         }
    196     }
    197 
    198     @Override
    199     public void writeStartElement(String namespaceURI, String localName,
    200             List<NamespaceDecl> decls) throws OAIException {
    201         try {
    202             for (NamespaceDecl decl : decls) {
    203                 writer.setPrefix(decl.getPrefix(), decl.getNamespaceURI());
    204             }
    205             writer.writeStartElement(namespaceURI, localName);
    206             boolean schemaDeclWritten = false;
    207             for (NamespaceDecl decl : decls) {
    208                 writer.writeNamespace(decl.getPrefix(), decl.getNamespaceURI());
    209                 if (decl.hasSchemaLocation()) {
    210                     /*
    211                      * From an XML point of view, the XSI-namespace is still in
    212                      * scope and this is not needed, but all other providers
    213                      * show this behavior.
    214                      */
    215                     if (!schemaDeclWritten) {
    216                         writer.writeNamespace("xsi",
    217                                 XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
    218                         schemaDeclWritten = true;
    219                     }
    220                     writer.writeAttribute(
    221                             XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
    222                             "schemaLocation", decl.getNamespaceURI() + " "
    223                                     + decl.getSchemaLocation());
    224                 }
    225             }
    226         } catch (XMLStreamException e) {
    227             throw new OAIException("error while serializing response", e);
    228         }
    229     }
    230 
    231     @Override
    232     public void writeEndElement() throws OAIException {
    233         try {
    234             writer.writeEndElement();
    235         } catch (XMLStreamException e) {
    236             throw new OAIException("error while serializing response", e);
    237         }
     195                writer.writeAttribute(
     196                        XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
     197                        "schemaLocation",
     198                        decl.getNamespaceURI() + " " + decl.getSchemaLocation());
     199            }
     200        }
     201    }
     202
     203    @Override
     204    public void writeEndElement() throws XMLStreamException {
     205        writer.writeEndElement();
    238206    }
    239207
    240208    @Override
    241209    public void writeAttribute(String localName, String value)
    242             throws OAIException {
    243         try {
    244             writer.writeAttribute(localName, value);
    245         } catch (XMLStreamException e) {
    246             throw new OAIException("error while serializing response", e);
    247         }
     210            throws XMLStreamException {
     211        writer.writeAttribute(localName, value);
    248212    }
    249213
    250214    @Override
    251215    public void writeAttribute(String namespaceURI, String localName,
    252             String value) throws OAIException {
    253         try {
    254             writer.writeAttribute(namespaceURI, localName, value);
    255         } catch (XMLStreamException e) {
    256             throw new OAIException("error while serializing response", e);
    257         }
    258     }
    259 
    260     @Override
    261     public void writeCharacters(String text) throws OAIException {
    262         try {
    263             writer.writeCharacters(text);
    264         } catch (XMLStreamException e) {
    265             throw new OAIException("error while serializing response", e);
    266         }
    267     }
    268 
    269     @Override
    270     public void writeDate(Date date) throws OAIException {
    271         try {
    272             FastDateFormat fmt = getDateFormat(repository.getGranularity());
    273             writer.writeCharacters(fmt.format(date));
    274         } catch (XMLStreamException e) {
    275             throw new OAIException("error while serializing response", e);
    276         }
    277     }
    278 
    279     @Override
    280     public void writeRecordHeader(Record record) throws OAIException {
    281         try {
    282             writer.writeStartElement("header");
    283             if (record.isDeleted()) {
    284                 writer.writeAttribute("status", "deleted");
    285             }
    286             writer.writeStartElement("identifier");
    287             writer.writeCharacters(
    288                     repository.createRecordId(record.getLocalId()));
    289             writer.writeEndElement(); // identifier element
    290             writer.writeStartElement("datestamp");
    291             writeDate(record.getDatestamp());
    292             writer.writeEndElement(); // datestamp element
    293             List<String> setSpecs = record.getSetSpecs();
    294             if (setSpecs != null) {
    295                 for (String setSpec : setSpecs) {
    296                     writer.writeStartElement("setSpec");
    297                     writer.writeCharacters(setSpec);
    298                     writer.writeEndElement(); // setSpec element
    299                 }
    300             }
    301             writer.writeEndElement(); // header element
    302         } catch (OAIException e) {
    303             throw e;
    304         } catch (XMLStreamException e) {
    305             throw new OAIException("error while serializing response", e);
    306         }
    307     }
    308 
    309     @Override
    310     public void writeRecord(Record record, MetadataFormat format)
    311             throws OAIException {
    312         try {
    313             writer.writeStartElement("record");
    314             writeRecordHeader(record);
    315             if (!record.isDeleted()) {
    316                 writer.writeStartElement("metadata");
    317                 format.writeObject(writer, record.getItem());
    318                 writer.writeEndElement(); // metadata element
    319             }
    320             writer.writeEndElement(); // record element
    321         } catch (OAIException e) {
    322             throw e;
    323         } catch (XMLStreamException e) {
    324             throw new OAIException("error writing record", e);
    325         }
     216            String value) throws XMLStreamException {
     217        writer.writeAttribute(namespaceURI, localName, value);
     218    }
     219
     220    @Override
     221    public void writeCharacters(String text) throws XMLStreamException {
     222        writer.writeCharacters(text);
     223    }
     224
     225    @Override
     226    public void writeDate(Date date) throws XMLStreamException {
     227        FastDateFormat fmt = getDateFormat(repository.getGranularity());
     228        writer.writeCharacters(fmt.format(date));
     229    }
     230
     231    @Override
     232    public void writeRecordHeader(Record record) throws XMLStreamException {
     233        writer.writeStartElement("header");
     234        if (record.isDeleted()) {
     235            writer.writeAttribute("status", "deleted");
     236        }
     237        writer.writeStartElement("identifier");
     238        writer.writeCharacters(repository.createRecordId(record.getLocalId()));
     239        writer.writeEndElement(); // identifier element
     240        writer.writeStartElement("datestamp");
     241        writeDate(record.getDatestamp());
     242        writer.writeEndElement(); // datestamp element
     243        List<String> setSpecs = record.getSetSpecs();
     244        if (setSpecs != null) {
     245            for (String setSpec : setSpecs) {
     246                writer.writeStartElement("setSpec");
     247                writer.writeCharacters(setSpec);
     248                writer.writeEndElement(); // setSpec element
     249            }
     250        }
     251        writer.writeEndElement(); // header element
     252    }
     253
     254    @Override
     255    public void writeRecord(Record record) throws XMLStreamException {
     256        writer.writeStartElement("record");
     257        writeRecordHeader(record);
     258        if (!record.isDeleted()) {
     259            writer.writeStartElement("metadata");
     260            // FIXME: re-work!
     261            record.writeRecord(writer);
     262            writer.writeEndElement(); // metadata element
     263        }
     264        writer.writeEndElement(); // record element
    326265    }
    327266
    328267    @Override
    329268    public void writeResumptionToken(ResumptionToken token)
    330         throws OAIException {
    331         try {
    332             writer.writeStartElement("resumptionToken");
    333             if (token != null) {
    334                 FastDateFormat fmt =
    335                     getDateFormat(Repository.Granularity.SECONDS);
    336                 writer.writeAttribute("expirationDate",
    337                         fmt.format(token.getExpirationDate()));
    338                 if (token.getCursor() >= 0) {
    339                     writer.writeAttribute("cursor",
    340                             Integer.toString(token.getCursor()));
    341                 }
    342                 if (token.getCompleteListSize() > 0) {
    343                     writer.writeAttribute("completeListSize",
    344                             Integer.toString(token.getCompleteListSize()));
    345                 }
    346                 writer.writeCharacters(Long.toString(token.getId()));
    347             }
    348             writer.writeEndElement(); // resumptionToken element
    349         } catch (XMLStreamException e) {
    350             throw new OAIException("error while serializing response", e);
    351         }
     269            throws XMLStreamException {
     270        writer.writeStartElement("resumptionToken");
     271        if (token != null) {
     272            FastDateFormat fmt = getDateFormat(Repository.Granularity.SECONDS);
     273            writer.writeAttribute("expirationDate",
     274                    fmt.format(token.getExpirationDate()));
     275            if (token.getCursor() >= 0) {
     276                writer.writeAttribute("cursor",
     277                        Integer.toString(token.getCursor()));
     278            }
     279            if (token.getCompleteListSize() > 0) {
     280                writer.writeAttribute("completeListSize",
     281                        Integer.toString(token.getCompleteListSize()));
     282            }
     283            writer.writeCharacters(Long.toString(token.getId()));
     284        }
     285        writer.writeEndElement(); // resumptionToken element
    352286    }
    353287
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/OAIProvider.java

    r1910 r1911  
    1616import javax.servlet.http.HttpServletRequest;
    1717import javax.servlet.http.HttpServletResponse;
     18import javax.xml.stream.XMLStreamException;
    1819
    1920import org.apache.commons.lang.time.DateUtils;
     
    2223
    2324import eu.clarin.oai.provider.OAIException;
    24 import eu.clarin.oai.provider.OAIRepositoryImplementationError;
    2525import eu.clarin.oai.provider.Repository;
    2626import eu.clarin.oai.provider.ext.Argument;
     
    3939    private static final String[] DATEFORMATS_FULL =
    4040        { "yyyy-MM-dd'T'HH:mm:ss'Z'" , "yyyy-MM-dd"};
    41     private static final OAIProvider s_instance = new OAIProvider();
    4241    private final List<Verb> verbs = new ArrayList<Verb>();
    4342    private final Map<Long, ResumptionToken> resumptionTokens =
    4443        new HashMap<Long, ResumptionToken>(64);
    4544    private Timer timer = new Timer("OAI-Provider-Maintenance", true);
    46     private AtomicBoolean isAvailable = new AtomicBoolean();
     45    private AtomicBoolean isAvailable = new AtomicBoolean(true);
    4746    private IPFilter ipfilter = new IPFilter();
    4847    private RepositoryAdapter repository;
    4948
    50     private OAIProvider() {
    51         super();
     49    private OAIProvider(Repository repository) throws OAIException {
     50        if (repository == null) {
     51            throw new NullPointerException("repository == null");
     52        }
     53       
    5254        // register basic verbs
    5355        this.registerVerb(new VerbIdentify());
     
    5759        this.registerVerb(new VerbListRecords());
    5860        this.registerVerb(new VerbGetRecord());
     61
     62        // set repository
     63        logger.debug("setting repository '{}'", repository.getId());
     64        this.repository = new RepositoryAdapterImpl(this, repository);
    5965
    6066        // provider maintenance timer
     
    8389    }
    8490
    85     public void setRepository(Repository repository) throws OAIException {
    86         if (repository == null) {
    87             throw new NullPointerException("repository == null");
    88         }
    89         if (this.repository != null) {
    90             throw new IllegalStateException("repository is already set");
    91         }
    92         logger.debug("setting repository '{}'", repository.getId());
    93         this.repository = new RepositoryAdapterImpl(this, repository);
    94         if (!isAvailable.compareAndSet(false, true)) {
    95             throw new IllegalStateException("unexpected state of isAvailable");
    96         }
    97     }
    98 
    9991    public boolean isAvailable() {
    10092        return isAvailable.get();
     
    10294
    10395    public void setIsAvailable(boolean value) {
    104         if (repository != null) {
    105             isAvailable.set(value);
    106         }
     96        isAvailable.set(value);
    10797    }
    10898
     
    115105    }
    116106
    117     public void process(HttpServletRequest request,
    118             HttpServletResponse response) throws OAIException {
    119         String remoteAddr = request.getRemoteAddr();
    120         if (remoteAddr == null) {
    121             throw new OAIException("provider expects valid ip address");
    122         }
    123         if (ipfilter.accept(remoteAddr)) {
    124             if (isAvailable.get()) {
     107    public void handleRequest(HttpServletRequest request,
     108            HttpServletResponse response) {
     109        try {
     110            final String remoteAddr = request.getRemoteAddr();
     111            if (ipfilter.accept(remoteAddr)) {
    125112                try {
    126                     doProcess(request, response);
    127                 } catch (OAIRepositoryImplementationError e) {
    128                     logger.error("repository implementation error", e);
    129                     sendHttpResponse(response,
     113                    if (isAvailable.get()) {
     114                        doProcess(request, response);
     115                    } else {
     116                        response.setHeader("Retry-After", "3600");
     117                        sendHttpResponse(response,
     118                                HttpServletResponse.SC_SERVICE_UNAVAILABLE,
     119                                "The OAI provider is currently not available.");
     120                    }
     121                } catch (OAIException e) {
     122                    logger.error("An error internal error occured", e);
     123                    sendHttpResponse(
     124                            response,
    130125                            HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
    131                             "OAI repository implementation error: " +
    132                                     e.getMessage());
     126                            "An internal error occured", e);
     127                } catch (XMLStreamException e) {
     128                    logger.error("An error occured while serializing reponse", e);
     129                    sendHttpResponse(
     130                            response,
     131                            HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
     132                            "An error occured while serializing reponse", e);
    133133                }
    134134            } else {
    135                 response.setHeader("Retry-After", "3600");
    136                 sendHttpResponse(response,
    137                                  HttpServletResponse.SC_SERVICE_UNAVAILABLE,
    138                                  "The OAI provider is currently not available.");
    139             }
    140         } else {
    141             logger.warn("request denied for remote host {}", remoteAddr);
    142             sendHttpResponse(response,
    143                              HttpServletResponse.SC_FORBIDDEN,
    144                              "The OAI provider will not serve you.");
     135                logger.warn("request denied for remote host {}", remoteAddr);
     136                sendHttpResponse(response, HttpServletResponse.SC_FORBIDDEN,
     137                        "The OAI provider will not serve you.");
     138            }
     139        } catch (IOException e) {
     140            /*
     141             * Well, can't really do anything useful here ...
     142             */
     143            logger.error("An unexpected exception occured", e);
    145144        }
    146145    }
    147146
    148147    private void doProcess(HttpServletRequest request,
    149             HttpServletResponse response) throws OAIException {
     148            HttpServletResponse response) throws IOException,
     149            XMLStreamException, OAIException {
    150150        VerbContextImpl ctx =
    151151            new VerbContextImpl(request, response, repository);
     
    237237        /*
    238238         * If any errors occurred create a proper response. NOTE: errors may
    239          * occur, when executing verb, so this block cannot be moved
     239         * occur, when processing verb, so this block cannot be moved
    240240         */
    241241        if (ctx.hasErrors()) {
     
    268268            verbs.add(verb);
    269269        } // synchronized (verbs)
    270     }
    271 
    272     public static OAIProvider instance() {
    273         return s_instance;
    274270    }
    275271
     
    399395    }
    400396
     397   
    401398    private void sendHttpResponse(HttpServletResponse response, int status,
    402             String message) throws OAIException {
    403         try {
    404             response.setStatus(status);
    405             response.setContentType("text/plain");
    406             PrintWriter out = response.getWriter();
    407             out.println(message);
    408             out.close();
    409         } catch (IOException e) {
    410             logger.error("OAI provider error while sending error to client", e);
    411             throw new OAIException("error", e);
    412         }
    413     }
     399            String message) throws IOException {
     400        sendHttpResponse(response, status, message, null);
     401    }
     402       
     403
     404    private void sendHttpResponse(HttpServletResponse response, int status,
     405            String message, Throwable t) throws IOException {
     406        response.setStatus(status);
     407        response.setContentType("text/plain");
     408        PrintWriter out = response.getWriter();
     409        out.println(message);
     410        if (logger.isDebugEnabled()) {
     411            t.printStackTrace(out);
     412        }
     413        out.close();
     414    }
     415
    414416} // class OAIProvider
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/RepositoryAdapterImpl.java

    r1910 r1911  
    99import eu.clarin.oai.provider.MetadataFormat;
    1010import eu.clarin.oai.provider.OAIException;
    11 import eu.clarin.oai.provider.OAIRepositoryImplementationError;
    1211import eu.clarin.oai.provider.Record;
    1312import eu.clarin.oai.provider.RecordList;
     
    3130
    3231        this.adminEmailAddresses = repository.getAdminAddreses();
    33         if (this.adminEmailAddresses == null) {
    34             throw new OAIRepositoryImplementationError(
    35                     " retured null");
    36         }
    37         if (this.adminEmailAddresses.isEmpty()) {
    38             throw new OAIRepositoryImplementationError(
    39                     "getAdminAddreses() returned empty set");
    40         }
     32//        if (this.adminEmailAddresses == null) {
     33//            throw new OAIRepositoryImplementationError(
     34//                    " retured null");
     35//        }
     36//        if (this.adminEmailAddresses.isEmpty()) {
     37//            throw new OAIRepositoryImplementationError(
     38//                    "getAdminAddreses() returned empty set");
     39//        }
    4140
    4241        // handle Dublin Core and do some sanity checks
    4342        Set<DublinCoreConverter> converters =
    4443            repository.getDublinCoreConverters();
    45         if (converters == null) {
    46             throw new OAIRepositoryImplementationError(
    47                     "getDublinCoreConverters() retruned null");
    48         }
    49         if (converters.isEmpty()) {
    50             throw new OAIRepositoryImplementationError(
    51                     "getDublinCoreConverters() retruned empty set");
    52         }
     44//        if (converters == null) {
     45//            throw new OAIRepositoryImplementationError(
     46//                    "getDublinCoreConverters() retruned null");
     47//        }
     48//        if (converters.isEmpty()) {
     49//            throw new OAIRepositoryImplementationError(
     50//                    "getDublinCoreConverters() retruned empty set");
     51//        }
    5352        this.metadataFormats.add(new DublinCoreMetadataFormat(converters));
    5453
     
    5655        Set<MetadataFormat> formats = repository.getCustomMetadataFormats();
    5756        if (formats != null) {
    58             Set<String> prefixes = new HashSet<String>();
     57//            Set<String> prefixes = new HashSet<String>();
    5958            for (MetadataFormat format : formats) {
    60                 String prefix = format.getPrefix();
    61                 if (prefix == null) {
    62                     throw new OAIRepositoryImplementationError(
    63                             "metadata format needs prefix non-null " +
    64                             "prefix (MetadataFormat: " + format + ")");
    65                 }
    66                 if (prefixes.contains(prefix)) {
    67                     throw new OAIRepositoryImplementationError(
    68                             "metadata prefix '" + prefix + "' is not unique" +
    69                             "in repository (MetadataFormat: " + format + ")");
    70                 }
    71                 if ("oai_dc".equals(prefix)) {
    72                     throw new OAIRepositoryImplementationError(
    73                             "dublin core metadata format must " +
    74                             "not be in the set of supported custom metadata " +
    75                             "formats (MetadataFormat: " + format + ")");
    76                 }
     59//                String prefix = format.getPrefix();
     60//                if (prefix == null) {
     61//                    throw new OAIRepositoryImplementationError(
     62//                            "metadata format needs prefix non-null " +
     63//                            "prefix (MetadataFormat: " + format + ")");
     64//                }
     65//                if (prefixes.contains(prefix)) {
     66//                    throw new OAIRepositoryImplementationError(
     67//                            "metadata prefix '" + prefix + "' is not unique" +
     68//                            "in repository (MetadataFormat: " + format + ")");
     69//                }
     70//                if ("oai_dc".equals(prefix)) {
     71//                    throw new OAIRepositoryImplementationError(
     72//                            "dublin core metadata format must " +
     73//                            "not be in the set of supported custom metadata " +
     74//                            "formats (MetadataFormat: " + format + ")");
     75//                }
    7776                // add format
    7877                this.metadataFormats.add(format);
     
    154153    @Override
    155154    public Set<MetadataFormat> getMetadataFormats(Record record) {
    156         final Class<?> clazz = record.getItem().getClass();
     155        // FIXME: re-work
     156//        final Class<?> clazz = record.getItem().getClass();
    157157        Set<MetadataFormat> result = null;
    158         for (MetadataFormat format : metadataFormats) {
    159             if (format.canWriteClass(clazz)) {
    160                 if (result == null) {
    161                     result = new HashSet<MetadataFormat>();
    162                 }
    163                 result.add(format);
    164             }
    165         }
     158//        for (MetadataFormat format : metadataFormats) {
     159//            if (format.canWriteClass(clazz)) {
     160//                if (result == null) {
     161//                    result = new HashSet<MetadataFormat>();
     162//                }
     163//                result.add(format);
     164//            }
     165//        }
    166166        if ((result == null) || result.isEmpty()) {
    167167            result = Collections.emptySet();
     
    205205
    206206    @Override
    207     public boolean getRecord(Record record, Object localId, boolean headerOnly)
    208             throws OAIException {
    209         try {
    210             final boolean result =
    211                 repository.getRecord(record, localId, headerOnly);
    212             if (result) {
    213                 checkRecord(record, headerOnly);
    214             }
    215             return result;
     207    public Record getRecord(Object localId) throws OAIException {
     208        try {
     209            return repository.getRecord(localId);
    216210        } catch (OAIException e) {
    217211            throw e;
     
    222216
    223217    @Override
     218    public Record getRecordHeader(Object localId) throws OAIException {
     219        try {
     220            return repository.getRecordHeader(localId);
     221        } catch (OAIException e) {
     222            throw e;
     223        } catch (Exception e) {
     224            throw new OAIException("error getting record", e);
     225        }
     226    }
     227
     228    @Override
    224229    public RecordList getRecords(String prefix, Date from, Date until,
    225             String set, int offset, boolean headerOnly) throws OAIException {
     230            String set, int offset) throws OAIException {
    226231        /*
    227232         * XXX: maybe add a OAIProvider defined upper limit?
     
    232237        }
    233238        try {
    234             return repository.getRecords(prefix, from, until, set, offset,
    235                     limit, headerOnly);
     239            return repository.getRecords(prefix, from, until, set, offset, limit);
    236240        } catch (OAIException e) {
    237241            throw e;
     
    242246
    243247    @Override
    244     public void checkRecord(Record record, boolean headerOnly)
    245             throws OAIRepositoryImplementationError {
    246         if (record.getLocalId() == null) {
    247             throw new OAIRepositoryImplementationError(
    248                     "localId must not be null");
    249         }
    250         if (record.getDatestamp() == null) {
    251             throw new OAIRepositoryImplementationError(
    252                     "datestamp must no be null");
    253         }
    254         if (!headerOnly) {
    255             if  (record.getItem() == null) {
    256                 throw new OAIRepositoryImplementationError(
    257                     "item must not be null, if full record is requested");
    258             }
     248    public RecordList getRecordHeaders(String prefix, Date from, Date until,
     249            String set, int offset) throws OAIException {
     250        /*
     251         * XXX: maybe add a OAIProvider defined upper limit?
     252         */
     253        int limit = repository.getPreferredResultListSize();
     254        if (limit < 1) {
     255            limit = Integer.MAX_VALUE;
     256        }
     257        try {
     258            return repository.getRecordHeaders(prefix, from, until, set, offset, limit);
     259        } catch (OAIException e) {
     260            throw e;
     261        } catch (Exception e) {
     262            throw new OAIException("error getting records", e);
    259263        }
    260264    }
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbContextImpl.java

    r1910 r1911  
    11package eu.clarin.oai.provider.impl;
    22
     3import java.io.IOException;
    34import java.io.OutputStream;
    45import java.util.ArrayList;
     
    1819import javax.servlet.http.HttpServletRequest;
    1920import javax.servlet.http.HttpServletResponse;
    20 
    21 import eu.clarin.oai.provider.OAIException;
     21import javax.xml.stream.XMLStreamException;
     22
    2223import eu.clarin.oai.provider.Repository;
    2324import eu.clarin.oai.provider.ext.Argument;
     
    190191
    191192    @Override
    192     public OAIOutputStream getOutputStream() throws OAIException {
     193    public OAIOutputStream getOutputStream() throws IOException,
     194            XMLStreamException {
    193195        int bestEnc = ENCODING_NONE;
    194196        float bestQvalue = 0.0f;
     
    255257                            bestQvalue = qvalue;
    256258                        }
    257                     } else {
    258                         throw new OAIException(
    259                                 "malformed Accept-Encoding header");
    260259                    }
    261260                } // while (tokens)
     
    285284        }
    286285
    287         try {
    288             response.setStatus(HttpServletResponse.SC_OK);
    289             response.setContentType("text/xml");
    290             response.setCharacterEncoding("utf-8");
    291 
    292             OutputStream out = null;
    293             switch (bestEnc) {
    294             case ENCODING_IDENITY:
    295                 out = response.getOutputStream();
    296                 break;
    297             case ENCODING_DEFLATE:
    298                 response.addHeader(HEADER_CONTENT_ENCODING, ENC_NAME_DEFLATE);
    299                 out = new DeflaterOutputStream(response.getOutputStream(),
    300                         new Deflater(Deflater.DEFAULT_COMPRESSION, true));
    301                 break;
    302             case ENCODING_GZIP:
    303                 response.addHeader(HEADER_CONTENT_ENCODING, ENC_NAME_GZIP);
    304                 out = new GZIPOutputStream(response.getOutputStream(),
    305                                            response.getBufferSize());
    306                 break;
    307             default:
    308                 throw new OAIException("no valid response encoding");
    309             }
    310             return new OAIOutputStreamImpl(this, out);
    311         } catch (Exception e) {
    312             throw new OAIException("error creating output stream", e);
    313         }
     286        response.setStatus(HttpServletResponse.SC_OK);
     287        response.setContentType("text/xml");
     288        response.setCharacterEncoding("utf-8");
     289
     290        OutputStream out = null;
     291        switch (bestEnc) {
     292        default:
     293            /* FALL-TROUGH */
     294        case ENCODING_IDENITY:
     295            out = response.getOutputStream();
     296            break;
     297        case ENCODING_DEFLATE:
     298            response.addHeader(HEADER_CONTENT_ENCODING, ENC_NAME_DEFLATE);
     299            out = new DeflaterOutputStream(response.getOutputStream(),
     300                    new Deflater(Deflater.DEFAULT_COMPRESSION, true));
     301            break;
     302        case ENCODING_GZIP:
     303            response.addHeader(HEADER_CONTENT_ENCODING, ENC_NAME_GZIP);
     304            out = new GZIPOutputStream(response.getOutputStream(),
     305                    response.getBufferSize());
     306            break;
     307        }
     308        return new OAIOutputStreamImpl(this, out);
    314309    }
    315310
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbEnumerateRecord.java

    r1910 r1911  
    11package eu.clarin.oai.provider.impl;
    22
     3import java.io.IOException;
    34import java.util.Date;
     5import java.util.List;
     6
     7import javax.xml.stream.XMLStreamException;
     8import javax.xml.stream.XMLStreamWriter;
    49
    510import eu.clarin.oai.provider.MetadataFormat;
    611import eu.clarin.oai.provider.OAIException;
    7 import eu.clarin.oai.provider.OAIRepositoryImplementationError;
    812import eu.clarin.oai.provider.Record;
    913import eu.clarin.oai.provider.RecordList;
     
    3438
    3539    @Override
    36     public final void process(VerbContext ctx) throws OAIException {
     40    public final void process(VerbContext ctx) throws IOException,
     41            XMLStreamException, OAIException {
    3742        logger.debug("process ENUMERATE-RECORD ({})", getName());
    3843
     
    7681            } else {
    7782                // fetch records
    78                 RecordList result = doGetRecords(repository, prefix, from,
    79                         until, set, offset);
     83                final RecordList result = doGetRecords(repository, prefix,
     84                        from, until, set, offset);
     85
    8086                // process results
    8187                if (result != null) {
     
    8490                        out.writeStartElement(getName());
    8591                        int size = 0;
    86                         RecordImpl record = new RecordImpl();
    87                         while (result.hasNextRecord()) {
    88                             result.nextRecord(record);
    89                             doCheckRecord(repository, record);
     92                       
     93                        // emulate a record ...
     94                        Record record = new Record() {
     95                            @Override
     96                            public Object getLocalId() {
     97                                return result.getLocalId();
     98                            }
     99
     100                            @Override
     101                            public Date getDatestamp() {
     102                                return result.getDatestamp();
     103                            }
     104
     105                            @Override
     106                            public boolean isDeleted() {
     107                                return result.isDeleted();
     108                            }
     109
     110                            @Override
     111                            public List<String> getSetSpecs() {
     112                                return result.getSetSpecs();
     113                            }
     114
     115                            public void writeRecord(XMLStreamWriter writer)
     116                                    throws XMLStreamException {
     117                                result.writeRecord(writer);
     118                            }
     119                        };
     120
     121                        // process results
     122                        while (result.next()) {
    90123                            doWriteRecord(repository, out, format, record);
    91                             record.reset();
    92124                            size++;
    93125                        } // while
     
    136168            throws OAIException;
    137169
    138     protected abstract void doCheckRecord(RepositoryAdapter repository,
    139             Record record) throws OAIRepositoryImplementationError;
    140 
    141170    protected abstract void doWriteRecord(RepositoryAdapter repository,
    142             OAIOutputStream out, MetadataFormat format, Record item)
    143             throws OAIException;
     171            OAIOutputStream out, MetadataFormat format, Record record)
     172            throws XMLStreamException;
    144173
    145174} // abstract class VerbEnumerateRecord
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbGetRecord.java

    r1910 r1911  
    11package eu.clarin.oai.provider.impl;
     2
     3import java.io.IOException;
     4
     5import javax.xml.stream.XMLStreamException;
    26
    37import eu.clarin.oai.provider.MetadataFormat;
    48import eu.clarin.oai.provider.OAIException;
     9import eu.clarin.oai.provider.Record;
    510import eu.clarin.oai.provider.ext.Argument;
    611import eu.clarin.oai.provider.ext.OAIErrorCode;
     
    2732
    2833    @Override
    29     public void process(VerbContext ctx) throws OAIException {
     34    public void process(VerbContext ctx) throws IOException,
     35            XMLStreamException, OAIException {
    3036        logger.debug("process GET-RECORD");
    3137
    3238        RepositoryAdapter repository = ctx.getRepository();
    3339        Object localId = ctx.getArgument(DefaultArguments.ARG_IDENTIFIER);
    34         RecordImpl record = new RecordImpl();
    35         if (repository.getRecord(record, localId, false)) {
     40
     41        final Record record = repository.getRecord(localId);
     42        if (record != null) {
    3643            String prefix =
    3744                (String) ctx.getArgument(DefaultArguments.ARG_METADATAPREFIX);
     
    4653                OAIOutputStream out = ctx.getOutputStream();
    4754                out.writeStartElement("GetRecord");
    48                 out.writeRecord(record, format);
     55                out.writeRecord(record);
    4956                out.writeEndElement(); // GetRecord element
    5057                out.close();
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbIdentify.java

    r1910 r1911  
    11package eu.clarin.oai.provider.impl;
    22
     3import java.io.IOException;
    34import java.util.Arrays;
    45import java.util.List;
     6
     7import javax.xml.stream.XMLStreamException;
    58
    69import eu.clarin.oai.provider.OAIException;
     
    811import eu.clarin.oai.provider.ext.Argument;
    912import eu.clarin.oai.provider.ext.OAIOutputStream;
     13import eu.clarin.oai.provider.ext.OAIOutputStream.NamespaceDecl;
    1014import eu.clarin.oai.provider.ext.RepositoryAdapter;
    1115import eu.clarin.oai.provider.ext.Verb;
    1216import eu.clarin.oai.provider.ext.VerbContext;
    13 import eu.clarin.oai.provider.ext.OAIOutputStream.NamespaceDecl;
    1417
    1518final class VerbIdentify extends Verb {
     
    3336
    3437    @Override
    35     public void process(VerbContext ctx) throws OAIException {
     38    public void process(VerbContext ctx) throws IOException,
     39            XMLStreamException, OAIException {
    3640        logger.debug("process IDENTIFY");
    3741
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbListIdentifiers.java

    r1910 r1911  
    33import java.util.Date;
    44
     5import javax.xml.stream.XMLStreamException;
     6
    57import eu.clarin.oai.provider.MetadataFormat;
    68import eu.clarin.oai.provider.OAIException;
    7 import eu.clarin.oai.provider.OAIRepositoryImplementationError;
    89import eu.clarin.oai.provider.Record;
    910import eu.clarin.oai.provider.RecordList;
     
    2223            String prefix, Date from, Date until, String set, int offset)
    2324            throws OAIException {
    24         return repository.getRecords(prefix, from, until, set, offset, true);
    25     }
    26 
    27     @Override
    28     protected void doCheckRecord(RepositoryAdapter repository, Record record)
    29             throws OAIRepositoryImplementationError {
    30         repository.checkRecord(record, true);
     25        return repository.getRecordHeaders(prefix, from, until, set, offset);
    3126    }
    3227
     
    3429    protected void doWriteRecord(RepositoryAdapter repository,
    3530            OAIOutputStream out, MetadataFormat format, Record record)
    36             throws OAIException {
     31            throws XMLStreamException {
    3732        out.writeRecordHeader(record);
    3833    }
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbListMetadataFormats.java

    r1910 r1911  
    11package eu.clarin.oai.provider.impl;
    22
     3import java.io.IOException;
    34import java.util.Set;
     5
     6import javax.xml.stream.XMLStreamException;
    47
    58import eu.clarin.oai.provider.MetadataFormat;
    69import eu.clarin.oai.provider.OAIException;
     10import eu.clarin.oai.provider.Record;
    711import eu.clarin.oai.provider.ext.Argument;
    812import eu.clarin.oai.provider.ext.OAIErrorCode;
     
    2832
    2933    @Override
    30     public void process(VerbContext ctx) throws OAIException {
     34    public void process(VerbContext ctx) throws IOException,
     35            XMLStreamException, OAIException {
    3136        logger.debug("process LIST-METADATA-FORMATS");
    3237
    33         RepositoryAdapter repository = ctx.getRepository();
     38        final RepositoryAdapter repository = ctx.getRepository();
    3439
    3540        Set<MetadataFormat> formats = null;
    3641        if (ctx.hasArgument(DefaultArguments.ARG_IDENTIFIER)) {
    37             Object localId = ctx.getArgument(DefaultArguments.ARG_IDENTIFIER);
    38             RecordImpl record = new RecordImpl();
    39             if (repository.getRecord(record, localId, false)) {
    40                 formats = repository.getMetadataFormats(record);
     42            final Object localId =
     43                    ctx.getArgument(DefaultArguments.ARG_IDENTIFIER);
     44            final Record record = repository.getRecordHeader(localId);
     45            if (record != null) {
     46// FIXME: re-work
     47//              formats = repository.getMetadataFormats(record);
     48                if ((formats == null) || formats.isEmpty()) {
     49                    ctx.addError(OAIErrorCode.NO_METADATA_FORMATS,
     50                            "No metadata formats available for record '" +
     51                                    repository.createRecordId(localId) + "'");
     52                }
    4153            } else {
    4254                ctx.addError(OAIErrorCode.ID_DOES_NOT_EXIST,
    4355                        "Record does not exist");
    4456            }
    45             record.reset();
    4657        } else {
    4758            formats = repository.getMetadataFormats();
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbListRecords.java

    r1910 r1911  
    33import java.util.Date;
    44
     5import javax.xml.stream.XMLStreamException;
     6
    57import eu.clarin.oai.provider.MetadataFormat;
    68import eu.clarin.oai.provider.OAIException;
    7 import eu.clarin.oai.provider.OAIRepositoryImplementationError;
    89import eu.clarin.oai.provider.Record;
    910import eu.clarin.oai.provider.RecordList;
    1011import eu.clarin.oai.provider.ext.OAIOutputStream;
    1112import eu.clarin.oai.provider.ext.RepositoryAdapter;
     13
    1214
    1315final class VerbListRecords extends VerbEnumerateRecord {
     
    1820    }
    1921
     22
    2023    @Override
    2124    protected RecordList doGetRecords(RepositoryAdapter repository,
    2225            String prefix, Date from, Date until, String set, int offset)
    2326            throws OAIException {
    24         return repository.getRecords(prefix, from, until, set, offset, false);
     27        return repository.getRecords(prefix, from, until, set, offset);
    2528    }
    2629
    27     @Override
    28     protected void doCheckRecord(RepositoryAdapter repository, Record record)
    29             throws OAIRepositoryImplementationError {
    30         repository.checkRecord(record, false);
    31     }
    3230
    3331    @Override
    3432    protected void doWriteRecord(RepositoryAdapter repository,
    3533            OAIOutputStream out, MetadataFormat format, Record record)
    36             throws OAIException {
    37         out.writeRecord(record, format);
     34            throws XMLStreamException {
     35        // FIXME: rework (send MetadataFormat?)
     36        out.writeRecord(record);
    3837    }
    3938
  • OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/VerbListSets.java

    r1910 r1911  
    11package eu.clarin.oai.provider.impl;
    22
     3import java.io.IOException;
    34import java.util.Arrays;
    45import java.util.List;
    56import java.util.Set;
     7
     8import javax.xml.stream.XMLStreamException;
    69
    710import eu.clarin.oai.provider.OAIException;
     
    3538
    3639    @Override
    37     public void process(VerbContext ctx) throws OAIException {
     40    public void process(VerbContext ctx) throws IOException,
     41            XMLStreamException, OAIException {
    3842        logger.debug("process LIST-SETS");
    3943
Note: See TracChangeset for help on using the changeset viewer.