Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIGatewayDescription.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIGatewayDescription.java	(revision 1173)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIGatewayDescription.java	(revision 1173)
@@ -0,0 +1,21 @@
+package eu.clarin.cmdi.oai.harvester;
+
+import java.net.URI;
+import java.util.List;
+
+public interface OAIGatewayDescription extends Description {
+    public static final String NAMESPACE_URI =
+        "http://www.openarchives.org/OAI/2.0/gateway/";
+    public static final String LOCAL_NAME = "gateway";
+
+    public String getSource();
+    
+    public URI getGatewayDescription();
+    
+    public List<String> getGatewayAdmins();
+
+    public URI getGatewayURL();
+    
+    public URI getGatewayNotes();
+
+} // interface OAIGatewayDescription
Index: OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIGatewayDescriptionParser.java
===================================================================
--- OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIGatewayDescriptionParser.java	(revision 1173)
+++ OAIHarvester/trunk/OAIHarvester/src/main/java/eu/clarin/cmdi/oai/harvester/OAIGatewayDescriptionParser.java	(revision 1173)
@@ -0,0 +1,198 @@
+package eu.clarin.cmdi.oai.harvester;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+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 OAIGatewayDescriptionParser implements DescriptionParser {
+    private static final class OAIGatewayDescriptionImpl implements
+            OAIGatewayDescription {
+        private final String source;
+        private final URI gatewayDescription;
+        private final List<String> gatewayAdmins;
+        private final URI gatewayURL;
+        private final URI gatewayNotes;
+
+        private OAIGatewayDescriptionImpl(String source,
+                URI gatewayDescription,
+                List<String> gatewayAdmins,
+                URI gatewayURL,
+                URI gatewayNotes) {
+            this.source = source;
+            this.gatewayDescription = gatewayDescription;
+            this.gatewayAdmins = Collections.unmodifiableList(gatewayAdmins);
+            this.gatewayURL = gatewayURL;
+            this.gatewayNotes = gatewayNotes;
+        }
+
+        @Override
+        public String getNamespaceURI() {
+            return NAMESPACE_URI;
+        }
+
+        @Override
+        public String getLocalName() {
+            return LOCAL_NAME;
+        }
+
+        @Override
+        public String getSource() {
+            return source;
+        }
+
+        @Override
+        public URI getGatewayDescription() {
+            return gatewayDescription;
+        }
+
+        @Override
+        public List<String> getGatewayAdmins() {
+            return gatewayAdmins;
+        }
+
+        @Override
+        public URI getGatewayURL() {
+            return gatewayURL;
+        }
+
+        @Override
+        public URI getGatewayNotes() {
+            return gatewayNotes;
+        }
+        
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("OAIGatewayDescription");
+            sb.append("[source=\"");
+            sb.append(source);
+            sb.append("\", gatewayDescription=");
+            sb.append(gatewayDescription);
+            sb.append(", gatewayAdmins=");
+            sb.append(gatewayAdmins);
+            if (gatewayURL != null) {
+                sb.append(", gatewayURL=");
+                sb.append(gatewayURL);
+            }
+            if (gatewayNotes != null) {
+                sb.append(", gatewayNotes=");
+                sb.append(gatewayNotes);
+            }
+            sb.append("]");
+            return sb.toString();
+        }
+    } // inner class OAIGatewayDescriptionImpl
+
+    private static final String TAG_SOURCE = "source";
+    private static final String TAG_GATEWAY_DESCRIPTION = "gatewayDescription";
+    private static final String TAG_GATEWAY_ADMIN = "gatewayAdmin";
+    private static final String TAG_GATEWAY_URL = "gatewayURL";
+    private static final String TAG_GATEWAY_NOTES = "gatewayNotes";
+
+    @Override
+    public String getNamespaceURI() {
+        return OAIGatewayDescription.NAMESPACE_URI;
+    }
+
+    @Override
+    public String getLocalName() {
+        return OAIGatewayDescription.LOCAL_NAME;
+    }
+
+    @Override
+    public Description parseDescription(XMLStreamReader reader)
+            throws XMLStreamException, HarvesterException {
+        reader.require(XMLStreamConstants.START_ELEMENT,
+                OAIGatewayDescription.NAMESPACE_URI,
+                OAIGatewayDescription.LOCAL_NAME);
+        reader.nextTag(); // skip "gateway" start tag
+
+        // source (required)
+        final String source = readContent(reader, TAG_SOURCE, true);
+
+        // gatewayDescription (required)
+        final URI gatewayDescription =
+            readContentAsURI(reader, TAG_GATEWAY_DESCRIPTION, true);
+
+        // gatewayAdmin (at least one)
+        final List<String> gatewayAdmins = new ArrayList<String>();
+        for (;;) {
+            final String admin =
+                readContent(reader, TAG_GATEWAY_ADMIN, gatewayAdmins.isEmpty());
+            if (admin == null) {
+                break;
+            }
+            gatewayAdmins.add(admin);
+        }
+
+        // gatewayURL (optional)
+        final URI gatewayURL = readContentAsURI(reader, TAG_GATEWAY_URL, false);
+
+        // gatewayNotes (optional)
+        final URI gatewayNotes =
+            readContentAsURI(reader, TAG_GATEWAY_NOTES, false);
+
+        reader.require(XMLStreamConstants.END_ELEMENT,
+                OAIGatewayDescription.NAMESPACE_URI,
+                OAIGatewayDescription.LOCAL_NAME);
+        reader.nextTag(); // skip "gateway" end tag
+
+        return new OAIGatewayDescriptionImpl(source, gatewayDescription,
+                gatewayAdmins, gatewayURL, gatewayNotes);
+    }
+
+    private static boolean check(XMLStreamReader reader, int type,
+            String namespaceURI, String localName) {
+        return (reader.getEventType() == type) &&
+                (namespaceURI.equals(reader.getNamespaceURI())) &&
+                (localName.equals(reader.getLocalName()));
+    }
+
+    private static String readContent(XMLStreamReader reader, String tag,
+            boolean required) throws XMLStreamException, HarvesterException {
+        if (!check(reader, XMLStreamConstants.START_ELEMENT,
+                OAIGatewayDescription.NAMESPACE_URI, tag)) {
+            if (!required) {
+                return null;
+            }
+            throw new XMLStreamException("element '{" +
+                    OAIGatewayDescription.NAMESPACE_URI + "}" + tag +
+                    "' expected at this position", reader.getLocation());
+        }
+        reader.next(); // skip start tag
+        String s = reader.getText();
+        if (s != null) {
+            s = s.trim();
+        }
+        if ((s == null) || s.isEmpty()) {
+            throw new HarvesterException("element '{" +
+                    OAIGatewayDescription.NAMESPACE_URI + "}" + tag +
+                    "' must not be empty");
+        }
+        reader.next(); // skip content
+        reader.require(XMLStreamConstants.END_ELEMENT,
+                OAIGatewayDescription.NAMESPACE_URI, tag);
+        reader.nextTag(); // skip up end tag
+        return s;
+    }
+
+    private static URI readContentAsURI(XMLStreamReader reader, String tag,
+            boolean required) throws XMLStreamException, HarvesterException {
+        try {
+            final String s = readContent(reader, tag, required);
+            return (s != null) ? new URI(s) : null;
+        } catch (URISyntaxException e) {
+            throw new HarvesterException("element '{" +
+                    OAIFriendsDescription.NAMESPACE_URI + "}" + tag +
+                    "' contains an illegal value", e);
+        }
+    }
+
+} // class OAIGatewaysDescriptionParser
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 1172)
+++ OAIHarvester/trunk/OAIHarvester/src/test/java/eu/clarin/cmdi/oai/harvester/HarvesterTest.java	(revision 1173)
@@ -204,4 +204,6 @@
             harvester.registerDescriptionParser(
                     new OAIFriendsDescriptionParser());
+            harvester.registerDescriptionParser(
+                    new OAIGatewayDescriptionParser());
 
             HarvestJob job = harvester.createJob(repos, handler);
