Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Description.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Description.java	(revision 1163)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Description.java	(revision 1163)
@@ -0,0 +1,9 @@
+package eu.clarin.cmdi.oai.harvester;
+
+public interface Description {
+    
+    public String getNamespaceURI();
+    
+    public String getLocalName();
+
+} // interface Description
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Harvester.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Harvester.java	(revision 1150)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Harvester.java	(revision 1163)
@@ -3,5 +3,10 @@
 import java.net.URI;
 
+import eu.clarin.cmdi.oai.harvester.ext.DescriptionParser;
+
 public interface Harvester {
+
+    public void registerDescriptionParser(DescriptionParser parser)
+            throws HarvesterException;
 
     public HarvestJob createJob(URI repositoryURI, HarvestHandler handler)
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIDublinCoreDescription.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIDublinCoreDescription.java	(revision 1163)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIDublinCoreDescription.java	(revision 1163)
@@ -0,0 +1,41 @@
+package eu.clarin.cmdi.oai.harvester;
+
+import java.util.List;
+
+
+public interface OAIDublinCoreDescription extends Description {
+    public static final String NAMESPACE_URI =
+        "http://www.openarchives.org/OAI/2.0/oai_dc/";
+    public static final String LOCAL_NAME = "dc";
+
+    public List<String> getTitle();
+
+    public List<String> getCreator();
+
+    public List<String> getSubject();
+
+    public List<String> getDescription();
+
+    public List<String> getPublishere();
+
+    public List<String> getContributor();
+
+    public List<String> getDate();
+
+    public List<String> getType();
+
+    public List<String> getFormat();
+
+    public List<String> getIdentifier();
+
+    public List<String> getSource();
+
+    public List<String> getLanguage();
+
+    public List<String> getRelation();
+
+    public List<String> getCoverage();
+
+    public List<String> getRight();
+
+} // interface OAIDublinCoreDescriuption
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIDublinCoreDescriptionParser.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIDublinCoreDescriptionParser.java	(revision 1163)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIDublinCoreDescriptionParser.java	(revision 1163)
@@ -0,0 +1,310 @@
+package eu.clarin.cmdi.oai.harvester;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import eu.clarin.cmdi.oai.harvester.ext.DescriptionParser;
+
+public final class OAIDublinCoreDescriptionParser implements DescriptionParser {
+    private static class OAIDublinCoreDescriptionImpl implements
+            OAIDublinCoreDescription {
+        private final List<String> title;
+        private final List<String> creator;
+        private final List<String> subject;
+        private final List<String> description;
+        private final List<String> publisher;
+        private final List<String> contributor;
+        private final List<String> date;
+        private final List<String> type;
+        private final List<String> format;
+        private final List<String> identifier;
+        private final List<String> source;
+        private final List<String> language;
+        private final List<String> relation;
+        private final List<String> coverage;
+        private final List<String> rights;
+
+        private OAIDublinCoreDescriptionImpl(Map<String, List<String>> data) {
+            this.title = get(data, "title");
+            this.creator = get(data, "creators");
+            this.subject = get(data, "subjects");
+            this.description = get(data, "description");
+            this.publisher = get(data, "publisher");
+            this.contributor = get(data, "contributor");
+            this.date = get(data, "date");
+            this.type = get(data, "type");
+            this.format = get(data, "format");
+            this.identifier = get(data, "identifier");
+            this.source = data.get("source");
+            this.language = get(data, "language");
+            this.relation = get(data, "relation");
+            this.coverage = get(data, "coverage");
+            this.rights = get(data, "rights");
+        }
+
+        @Override
+        public String getNamespaceURI() {
+            return NAMESPACE_URI;
+        }
+
+        @Override
+        public String getLocalName() {
+            return LOCAL_NAME;
+        }
+
+        @Override
+        public List<String> getTitle() {
+            return title;
+        }
+        
+        @Override
+        public List<String> getCreator() {
+            return creator;
+        }
+        
+        @Override
+        public List<String> getSubject() {
+            return subject;
+        }
+        
+        @Override
+        public List<String> getDescription() {
+            return description;
+        }
+        
+        @Override
+        public List<String> getPublishere() {
+            return publisher;
+        }
+        
+        @Override
+        public List<String> getContributor() {
+            return contributor;
+        }
+        
+        @Override
+        public List<String> getDate() {
+            return date;
+        }
+        
+        @Override
+        public List<String> getType() {
+            return type;
+        }
+        
+        @Override
+        public List<String> getFormat() {
+            return format;
+        }
+        
+        @Override
+        public List<String> getIdentifier() {
+            return identifier;
+        }
+        
+        @Override
+        public List<String> getSource() {
+            return source;
+        }
+        
+        @Override
+        public List<String> getLanguage() {
+            return language;
+        }
+        
+        @Override
+        public List<String> getRelation() {
+            return relation;
+        }
+        
+        @Override
+        public List<String> getCoverage() {
+            return coverage;
+        }
+        
+        @Override
+        public List<String> getRight() {
+            return rights;
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder("OAIDublinCoreDescription");
+            sb.append("[");
+            int i = 0;
+            if (title != null) {
+                sb.append("title=");
+                sb.append(title);
+                i++;
+            }
+            if (creator != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("creator=");
+                sb.append(creator);
+                
+            }
+            if (subject != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("subject=");
+                sb.append(subject);
+            }
+            if (description != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("descripton=");
+                sb.append(description);
+            }
+            if (publisher != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("publisher=");
+                sb.append(publisher);
+            }
+            if (contributor != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("contributor=");
+                sb.append(contributor);
+            }
+            if (date != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("date=");
+                sb.append(date);
+            }
+            if (type != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("type=");
+                sb.append(type);
+            }
+            if (format != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("format=");
+                sb.append(format);
+            }
+            if (identifier != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("identifier=");
+                sb.append(identifier);
+            }
+            if (source != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("source=");
+                sb.append(source);
+            }
+            if (language != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("language=");
+                sb.append(language);
+            }
+            if (relation != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("relation=");
+                sb.append(relation);
+            }
+            if (coverage != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("coverage=");
+                sb.append(coverage);
+            }
+            if (rights != null) {
+                if (i++ > 0) {
+                    sb.append(", ");
+                }
+                sb.append("rights=");
+                sb.append(rights);
+            }
+            sb.append("]");
+            return sb.toString();
+        }
+
+        private static List<String> get(Map<String, List<String>> data,
+                String key) {
+            List<String> list = data.get(key);
+            if (list != null) {
+                if (!list.isEmpty()) {
+                    list = Collections.unmodifiableList(list);
+                } else {
+                    list = null;
+                }
+            }
+            return list;
+        }
+
+    } // inner class OAIDublinCoreDescription
+
+    private static final String DC_NS = "http://purl.org/dc/elements/1.1/";
+
+    
+    @Override
+    public String getNamespaceURI() {
+        return OAIDublinCoreDescription.NAMESPACE_URI;
+    }
+
+    @Override
+    public String getLocalName() {
+        return OAIDublinCoreDescription.LOCAL_NAME;
+    }
+
+    @Override
+    public Description parseDescription(XMLStreamReader reader)
+            throws XMLStreamException, HarvesterException {
+        Map<String, List<String>> data = null;
+        reader.nextTag(); // skip "dc" end tag
+        for (;;) {
+            if (!DC_NS.equals(reader.getNamespaceURI())) {
+                break;
+            }
+            reader.require(XMLStreamConstants.START_ELEMENT, null, null);
+            final String localName = reader.getLocalName()
+                .trim().toLowerCase().intern();
+            reader.next(); // skip start tag
+            final String value = reader.getText().trim();
+            reader.next(); // skip content
+            reader.require(XMLStreamConstants.END_ELEMENT, DC_NS, localName);
+            reader.nextTag();
+            if (!value.isEmpty()) {
+                if (data == null) {
+                    data = new HashMap<String, List<String>>();
+                }
+                List<String> list = data.get(localName);
+                if (list == null) {
+                    list = new ArrayList<String>();
+                    data.put(localName, list);
+                }
+                list.add(value);
+            }
+        } // for
+        reader.nextTag(); // skip "dc" end tag
+        return (data != null) ? new OAIDublinCoreDescriptionImpl(data) : null;
+    }
+
+} // class OAIDublinCoreDescriptionParser
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIIdentifierDescription.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIIdentifierDescription.java	(revision 1163)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIIdentifierDescription.java	(revision 1163)
@@ -0,0 +1,18 @@
+package eu.clarin.cmdi.oai.harvester;
+
+
+public interface OAIIdentifierDescription extends Description {
+    public static final String NAMESPACE_URI =
+        "http://www.openarchives.org/OAI/2.0/oai-identifier";
+    public static final String LOCAL_NAME =
+        "oai-identifier";
+
+    public String getScheme();
+
+    public String getRepositoryIdentifier();
+
+    public String getDelimiter();
+
+    public String getSampleIdentifier();
+
+} // interface OAIIdentifierDescription
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIIdentifierDescriptionParser.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIIdentifierDescriptionParser.java	(revision 1163)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIIdentifierDescriptionParser.java	(revision 1163)
@@ -0,0 +1,152 @@
+package eu.clarin.cmdi.oai.harvester;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import eu.clarin.cmdi.oai.harvester.ext.DescriptionParser;
+
+public final class OAIIdentifierDescriptionParser implements DescriptionParser {
+    private static class OAIIdentifierDescriptionImpl implements
+            OAIIdentifierDescription {
+        private final String repositoryIdentifier;
+        private final String delimiter;
+        private final String sampleIdentifier;
+
+        private OAIIdentifierDescriptionImpl(String repositoryIdentifier,
+                String delimiter, String sampleIdentifier) {
+            this.repositoryIdentifier = repositoryIdentifier;
+            this.delimiter = delimiter;
+            this.sampleIdentifier = sampleIdentifier;
+        }
+
+        @Override
+        public String getNamespaceURI() {
+            return NAMESPACE_URI;
+        }
+
+        @Override
+        public String getLocalName() {
+            return LOCAL_NAME;
+        }
+
+        @Override
+        public String getScheme() {
+            return OAI_IDENTIFIER_SCHEME;
+        }
+
+        @Override
+        public String getRepositoryIdentifier() {
+            return repositoryIdentifier;
+        }
+
+        @Override
+        public String getDelimiter() {
+            return delimiter;
+        }
+
+        @Override
+        public String getSampleIdentifier() {
+            return sampleIdentifier;
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder("OAIIdentifierDescription");
+            sb.append("[\"");
+            sb.append(OAI_IDENTIFIER_SCHEME);
+            sb.append("\", \"");
+            sb.append(repositoryIdentifier);
+            sb.append("\", \"");
+            sb.append(delimiter);
+            sb.append("\", \"");
+            sb.append(sampleIdentifier);
+            sb.append("\"]");
+            return sb.toString();
+        }
+
+    } // inner class OAIIdentifierDescriptionImpl
+
+    private static final String TAG_SCHEME =
+        "scheme";
+    private static final String TAG_REPOSITORY_IDENTIFIER =
+        "repositoryIdentifier";
+    private static final String TAG_DELIMTER =
+        "delimiter";
+    private static final String TAG_SAMPLE_IDENTIFIER =
+        "sampleIdentifier";
+    private static final String OAI_IDENTIFIER_SCHEME = "oai";
+
+    @Override
+    public String getNamespaceURI() {
+        return OAIIdentifierDescription.NAMESPACE_URI;
+    }
+
+    @Override
+    public String getLocalName() {
+        return OAIIdentifierDescription.LOCAL_NAME;
+    }
+
+    @Override
+    public Description parseDescription(XMLStreamReader reader)
+            throws XMLStreamException, HarvesterException {
+        reader.require(XMLStreamConstants.START_ELEMENT,
+                OAIIdentifierDescription.NAMESPACE_URI,
+                OAIIdentifierDescription.LOCAL_NAME);
+        reader.nextTag(); // skip "oai-identifier" start tag
+        reader.require(XMLStreamConstants.START_ELEMENT,
+                OAIIdentifierDescription.NAMESPACE_URI,
+                TAG_SCHEME);
+        reader.next(); // skip "schema" start tag
+        final String scheme = readText(TAG_SCHEME, reader);
+        if (!OAI_IDENTIFIER_SCHEME.equals(scheme)) {
+            throw new HarvesterException("oai-indentifier scheme must be '" +
+                    OAI_IDENTIFIER_SCHEME + "'");
+        }
+        reader.next(); // skip content
+        reader.nextTag(); // "schema" end tag
+        reader.require(XMLStreamConstants.START_ELEMENT,
+                OAIIdentifierDescription.NAMESPACE_URI,
+                TAG_REPOSITORY_IDENTIFIER);
+        reader.next(); // skip "repositoryIdentifier" start tag
+        final String repositoryIdentifier =
+            readText(TAG_REPOSITORY_IDENTIFIER, reader);
+        reader.next(); // content
+        reader.nextTag(); // skip "repositoryIdentifier" end tag
+        reader.require(XMLStreamConstants.START_ELEMENT,
+                OAIIdentifierDescription.NAMESPACE_URI,
+                TAG_DELIMTER);
+        reader.next(); // skip "delimiter" start tag
+        final String delimiter = readText(TAG_DELIMTER, reader);
+        reader.next(); // advance
+        reader.nextTag(); // skip "delimiter" end tag
+        reader.require(XMLStreamConstants.START_ELEMENT,
+                OAIIdentifierDescription.NAMESPACE_URI,
+                TAG_SAMPLE_IDENTIFIER);
+        reader.next(); // skip "sampleIdentifier" start tag
+        final String sampleIdentifier =
+            readText(TAG_SAMPLE_IDENTIFIER, reader);
+        reader.next(); // advance
+        reader.nextTag(); // skip "sampleIdentifier" end tag
+        reader.require(XMLStreamConstants.END_ELEMENT,
+                OAIIdentifierDescription.NAMESPACE_URI,
+                OAIIdentifierDescription.LOCAL_NAME);
+        reader.nextTag(); // skip "oai-identifier" end tag  
+        return new OAIIdentifierDescriptionImpl(repositoryIdentifier,
+                delimiter, sampleIdentifier);
+    }
+
+    private static String readText(String tag, XMLStreamReader reader)
+            throws XMLStreamException, HarvesterException {
+        String s = reader.getText();
+        if (s != null) {
+            s = s.trim();
+        }
+        if ((s == null) || s.isEmpty()) {
+            throw new HarvesterException("element '{" +
+                    OAIIdentifierDescription.NAMESPACE_URI + "}" + tag +
+                    "' must not be empty");
+        }
+        return s;
+    }
+
+} // class OAIIdentifierDescriptionParser
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Repository.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Repository.java	(revision 1150)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/Repository.java	(revision 1163)
@@ -1,3 +1,4 @@
 package eu.clarin.cmdi.oai.harvester;
+import java.util.Date;
 import java.util.List;
 
@@ -25,5 +26,5 @@
     public List<String> getAdminEmail();
 
-    public String getEarliestTimestamp();
+    public Date getEarliestTimestamp();
 
     public DeletedNotion getDeletedNotion();
@@ -33,3 +34,5 @@
     public int getCompressionMask();
 
+    public List<Description> getDescriptions();
+
 } // interface Repository
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/ext/DescriptionParser.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/ext/DescriptionParser.java	(revision 1163)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/ext/DescriptionParser.java	(revision 1163)
@@ -0,0 +1,18 @@
+package eu.clarin.cmdi.oai.harvester.ext;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import eu.clarin.cmdi.oai.harvester.Description;
+import eu.clarin.cmdi.oai.harvester.HarvesterException;
+
+public interface DescriptionParser {
+
+    public String getNamespaceURI();
+
+    public String getLocalName();
+
+    public Description parseDescription(XMLStreamReader reader)
+        throws XMLStreamException, HarvesterException;
+
+} // interface DescriptionParser
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/AbstractHarvester.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/AbstractHarvester.java	(revision 1150)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/AbstractHarvester.java	(revision 1163)
@@ -4,4 +4,6 @@
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.xml.stream.XMLInputFactory;
@@ -16,4 +18,5 @@
 import eu.clarin.cmdi.oai.harvester.Harvester;
 import eu.clarin.cmdi.oai.harvester.HarvesterException;
+import eu.clarin.cmdi.oai.harvester.ext.DescriptionParser;
 
 abstract class AbstractHarvester implements Harvester {
@@ -22,4 +25,6 @@
     private final XMLInputFactory factory;
 //    private final XMLValidationSchema schema;
+    private List<DescriptionParser> descriptionParsers =
+        new ArrayList<DescriptionParser>();
 
     protected AbstractHarvester() {
@@ -41,4 +46,21 @@
     }
 
+    public final void registerDescriptionParser(DescriptionParser parser)
+            throws HarvesterException {
+        if (parser == null) {
+            throw new NullPointerException("parser == null");
+        }
+        if (findDescriptionParser(parser.getNamespaceURI(),
+                parser.getLocalName()) != null) {
+            throw new HarvesterException("description parser for '{" +
+                    parser.getNamespaceURI() + "}" + parser.getLocalName() +
+                    "' was already registered");
+        }
+        if (descriptionParsers == null) {
+            descriptionParsers = new ArrayList<DescriptionParser>();
+        }
+        descriptionParsers.add(parser);
+    }
+    
     public abstract HarvestJob createJob(URI repositoryURI,
             HarvestHandler handler) throws HarvesterException;
@@ -94,4 +116,17 @@
     }
 
+    DescriptionParser findDescriptionParser(String namespaceURI,
+            String localName) {
+        if (descriptionParsers != null) {
+            for (DescriptionParser parser : descriptionParsers) {
+                if (namespaceURI.equals(parser.getNamespaceURI()) &&
+                        localName.equals(parser.getLocalName())) {
+                    return parser;
+                }
+            }
+        }
+        return null;
+    }
+
     abstract void doRunJob(HarvestJobImpl job) throws HarvesterException;
 
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/HarvestJobImpl.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/HarvestJobImpl.java	(revision 1150)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/HarvestJobImpl.java	(revision 1163)
@@ -13,4 +13,5 @@
 import javax.xml.stream.XMLStreamReader;
 
+import eu.clarin.cmdi.oai.harvester.Description;
 import eu.clarin.cmdi.oai.harvester.HarvestHandler;
 import eu.clarin.cmdi.oai.harvester.HarvestJob;
@@ -109,8 +110,9 @@
     private String protocolVersion;
     private List<String> adminEmail = Collections.emptyList();
-    private String earliestTimestamp;
+    private Date earliestTimestamp;
     private DeletedNotion deletedNotion;
     private Granularity granularity;
     private int compressionMask;
+    private List<Description> descriptions;
     private List<MetadataFormat> metadataFormats;
     private long totalRecordCount = 0;
@@ -277,5 +279,5 @@
             return Collections.unmodifiableList(statistics);
         } else {
-            return null;
+            return Collections.emptyList();
         }
     }
@@ -355,5 +357,5 @@
     }
 
-    void setEarliestTimestamp(String earliestTimestamp) {
+    void setEarliestTimestamp(Date earliestTimestamp) {
         this.earliestTimestamp = earliestTimestamp;
     }
@@ -377,4 +379,8 @@
     int getCompressionMask() {
         return compressionMask;
+    }
+
+    void setDescriptions(List<Description> descriptions) {
+        this.descriptions = descriptions;
     }
 
@@ -539,5 +545,5 @@
                 
                 @Override
-                public String getEarliestTimestamp() {
+                public Date getEarliestTimestamp() {
                     return earliestTimestamp;
                 }
@@ -560,5 +566,12 @@
                 @Override
                 public List<String> getAdminEmail() {
-                    return adminEmail;
+                    return (adminEmail != null) ?
+                            Collections.unmodifiableList(adminEmail) : null;
+                }
+
+                @Override
+                public List<Description> getDescriptions() {
+                    return (descriptions != null) ?
+                            Collections.unmodifiableList(descriptions) : null;
                 }
             };
@@ -570,5 +583,6 @@
         this.metadataFormats = metadataFormats;
         if (handler != null) {
-            handler.onListMetadataFormats(this.metadataFormats);
+            handler.onListMetadataFormats(
+                    Collections.unmodifiableList(this.metadataFormats));
         }
     }
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/HarvestWorker.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/HarvestWorker.java	(revision 1150)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/HarvestWorker.java	(revision 1163)
@@ -25,4 +25,5 @@
 import org.joda.time.format.DateTimeFormatter;
 
+import eu.clarin.cmdi.oai.harvester.Description;
 import eu.clarin.cmdi.oai.harvester.HarvestJob.State;
 import eu.clarin.cmdi.oai.harvester.HarvesterException;
@@ -31,4 +32,5 @@
 import eu.clarin.cmdi.oai.harvester.Repository.DeletedNotion;
 import eu.clarin.cmdi.oai.harvester.Repository.Granularity;
+import eu.clarin.cmdi.oai.harvester.ext.DescriptionParser;
 import eu.clarin.cmdi.oai.harvester.impl.HarvestJobImpl.Task;
 
@@ -187,6 +189,10 @@
             job.setAdminEmail(adminEmail);
 
-            s = response.readContent(OAI_NS, "earliestDatestamp", true);
-            job.setEarliestTimestamp(s);
+            /*
+             *  defer setting of earliestDatestamp in job until we know
+             *  the datestamp granularity of the repository 
+             */
+            final String earliestDatestamp =
+                response.readContent(OAI_NS, "earliestDatestamp", true);
 
             s = response.readContent(OAI_NS, "deletedRecord", true);
@@ -203,12 +209,15 @@
 
             s = response.readContent(OAI_NS, "granularity", true);
+            Granularity granularity = null;
             if (GRANULARITY_DAYS.equals(s)) {
-                job.setGranularity(Granularity.DAYS);
+                granularity = Granularity.DAYS;
             } else if (GRANULARITY_SECONDS.equals(s)) {
-                job.setGranularity(Granularity.SECONDS);
+                granularity = Granularity.SECONDS;
             } else {
                 throw new HarvesterException(
                         "invalid value for 'granularity': " + s);
             }
+            job.setGranularity(granularity);
+            job.setEarliestTimestamp(parseDate(granularity, earliestDatestamp));
 
             int mask = 0;
@@ -225,9 +234,26 @@
             job.setCompressionMask(mask);
 
+            List<Description> descriptions = null;
             while (response.readStart(OAI_NS, "description", false)) {
                 response.consumeWhitespace();
                 final String namespaceURI = response.readNamespaceURI();
-                System.err.println("DESCRIPTION: URI = " + namespaceURI);
+                final String localName = response.peekElementLocalName();
+                DescriptionParser parser = harvester.findDescriptionParser(
+                        namespaceURI, localName);
+                if (parser != null) {
+                    Description desc =
+                        parser.parseDescription(response.getXMLStreamReader());
+                    if (desc != null) {
+                        if (descriptions == null) {
+                            descriptions = new ArrayList<Description>();
+                        }
+                        descriptions.add(desc);
+                    }
+                } else {
+                    System.err.println("skipping DESC " + namespaceURI + ", " +
+                            localName);
+                }
                 response.readEnd(OAI_NS, "description", true);
+                job.setDescriptions(descriptions);
             }
             response.readEnd(OAI_NS, "Identify");
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/Response.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/Response.java	(revision 1150)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/impl/Response.java	(revision 1163)
@@ -286,4 +286,12 @@
         }
         return reader.getNamespaceURI();
+    }
+
+    public String peekElementLocalName() throws XMLStreamException {
+        if (!reader.isStartElement()) {
+            throw new XMLStreamException("not at a start elment event",
+                    reader.getLocation());
+        }
+        return reader.getLocalName();
     }
 
Index: OAIHarvester/trunk/OAIHarvester/src/test/java/eu/clarin/cmdi/oai/harvester/HarvesterTest.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/test/java/eu/clarin/cmdi/oai/harvester/HarvesterTest.java	(revision 1150)
+++ OAIHarvester/trunk/OAIHarvester/src/test/java/eu/clarin/cmdi/oai/harvester/HarvesterTest.java	(revision 1163)
@@ -89,4 +89,10 @@
             }
             System.err.println("        compression: " + sb.toString());
+            final List<Description> descs = repository.getDescriptions();
+            if (descs != null) {
+                for (Description desc : descs) {
+                    System.err.println("        description: " + desc);
+                }
+            }
         }
         
@@ -189,6 +195,12 @@
 
         Harvester harvester = SimpleHarvester.newInstance();
-        harvester.setMaxNetworkRetryCount(16);
         try {
+            // configure harvester
+            harvester.setMaxNetworkRetryCount(16);
+            harvester.registerDescriptionParser(
+                    new OAIIdentifierDescriptionParser());
+            harvester.registerDescriptionParser(
+                    new OAIDublinCoreDescriptionParser());
+
             HarvestJob job = harvester.createJob(repos, handler);
 //            Calendar cal = Calendar.getInstance(TimeZone.getDefault());
