diff --git a/.gitignore b/.gitignore index 3df9a13..15010ba 100644 --- a/.gitignore +++ b/.gitignore @@ -82,7 +82,8 @@ local.properties .scala_dependencies .worksheet -# Uncomment this line if you wish to ignore the project description file. -# Typically, this file would be tracked if it contains build/dependency configurations: -#.project - +# my exclusions +.project +.classpath +.java-version +target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c4c5cb6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + eu.dnetlib + dnet-oai-server + 0.0.1-SNAPSHOT + dnet-oai-server + Simple OAI-PMH server + + 17 + + + + + org.dom4j + dom4j + 2.1.4 + + + + jaxen + jaxen + + + + org.apache.commons + commons-lang3 + + + + commons-codec + commons-codec + + + + commons-io + commons-io + 2.16.1 + + + + org.postgresql + postgresql + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-json + + + + com.fasterxml.jackson.module + jackson-module-jakarta-xmlbind-annotations + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + org.mockito + mockito-core + test + + + + org.mockito + mockito-junit-jupiter + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.apache.maven.plugins + maven-help-plugin + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..8380a69 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/main/.DS_Store b/src/main/.DS_Store new file mode 100644 index 0000000..a06ac0f Binary files /dev/null and b/src/main/.DS_Store differ diff --git a/src/main/java/eu/dnetlib/apps/oai/OaiServerApplication.java b/src/main/java/eu/dnetlib/apps/oai/OaiServerApplication.java new file mode 100644 index 0000000..d849d8f --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/OaiServerApplication.java @@ -0,0 +1,13 @@ +package eu.dnetlib.apps.oai; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class OaiServerApplication { + + public static void main(final String[] args) { + SpringApplication.run(OaiServerApplication.class, args); + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/OaiServerConf.java b/src/main/java/eu/dnetlib/apps/oai/OaiServerConf.java new file mode 100644 index 0000000..2a8e7eb --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/OaiServerConf.java @@ -0,0 +1,42 @@ +package eu.dnetlib.apps.oai; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +@Component +@Configuration +@ConfigurationProperties(prefix = "oai.server") +public class OaiServerConf { + + private String baseUrl; + + private String repositoryName; + + private String adminEmail; + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(final String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getRepositoryName() { + return repositoryName; + } + + public void setRepositoryName(final String repositoryName) { + this.repositoryName = repositoryName; + } + + public String getAdminEmail() { + return adminEmail; + } + + public void setAdminEmail(final String adminEmail) { + this.adminEmail = adminEmail; + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/OaiServerController.java b/src/main/java/eu/dnetlib/apps/oai/OaiServerController.java new file mode 100644 index 0000000..719fc72 --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/OaiServerController.java @@ -0,0 +1,282 @@ +package eu.dnetlib.apps.oai; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import eu.dnetlib.apps.oai.domain.OaiError; +import eu.dnetlib.apps.oai.domain.OaiMetadataFormat; +import eu.dnetlib.apps.oai.domain.OaiPage; +import eu.dnetlib.apps.oai.domain.OaiRecord; +import eu.dnetlib.apps.oai.domain.OaiSet; +import eu.dnetlib.apps.oai.domain.OaiVerb; +import eu.dnetlib.apps.oai.utils.DateUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Controller +public class OaiServerController { + + private static final String DEFAULT_CONTENT_TYPE = "text/xml;charset=utf-8"; + + @Autowired + private OaiService oaiService; + + @Autowired + private OaiServerConf oaiConf; + + private static final Log log = LogFactory.getLog(OaiServerController.class); + + @RequestMapping("/oai") + public void oaiCall(final HttpServletRequest request, final HttpServletResponse response) throws IOException { + response.setContentType(OaiServerController.DEFAULT_CONTENT_TYPE); + + final Map params = cleanParameters(request.getParameterMap()); + + try (final OutputStream out = response.getOutputStream()) { + IOUtils.write(oaiResponse(params), out, StandardCharsets.UTF_8); + } + + } + + private String oaiResponse(final Map params) { + if (params == null) { return prepareErrorResponseXml(OaiError.badArgument); } + + final String verb = params.remove("verb"); + + return switch (OaiVerb.validate(verb)) { + case IDENTIFY -> oaiIdentify(params); + case LIST_METADATA_FORMATS -> oaiListMetadataFormats(params); + case LIST_SETS -> oaiListSets(params); + case GET_RECORD -> oaiGetRecord(params); + case LIST_IDENTIFIERS -> oaiListIdentifiers(params); + case LIST_RECORDS -> oaiListRecords(params); + default -> prepareErrorResponseXml(OaiError.badVerb); + }; + } + + private String oaiIdentify(final Map params) { + if (!params.isEmpty()) { return prepareErrorResponseXml(OaiError.badArgument); } + + final Document doc = genericOaiResponse(OaiVerb.IDENTIFY.getVerb()); + final Element dataNode = doc.getRootElement().addElement(OaiVerb.IDENTIFY.getVerb()); + + dataNode.addElement("baseURL").setText(oaiConf.getBaseUrl()); + dataNode.addElement("repositoryName").setText(oaiConf.getRepositoryName()); + dataNode.addElement("protocolVersion").setText("2.0"); + dataNode.addElement("adminEmail").setText(oaiConf.getAdminEmail()); + dataNode.addElement("earliestDatestamp").setText("1900-01-01T00:00:00Z"); + dataNode.addElement("deletedRecord").setText("transient"); + dataNode.addElement("granularity").setText("YYYY-MM-DDThh:mm:ssZ"); + + return doc.asXML(); + } + + private String oaiListMetadataFormats(final Map params) { + final String id = params.remove("identifier"); + if (!params.isEmpty()) { return prepareErrorResponseXml(OaiError.badArgument); } + + final Document doc = genericOaiResponse(OaiVerb.LIST_METADATA_FORMATS.getVerb()); + final Element dataNode = doc.getRootElement().addElement(OaiVerb.LIST_METADATA_FORMATS.getVerb()); + + final List formats = + StringUtils.isBlank(id) ? this.oaiService.listMetadataFormats(id) : this.oaiService.listMetadataFormats(); + + for (final OaiMetadataFormat oaiFormat : formats) { + final Element formatNode = dataNode.addElement("metadataFormat"); + formatNode.addElement("metadataPrefix").setText(oaiFormat.getMetadataPrefix()); + formatNode.addElement("schema").setText(oaiFormat.getMetadataSchema()); + formatNode.addElement("metadataNamespace").setText(oaiFormat.getMetadataNamespace()); + } + return doc.asXML(); + } + + private String oaiListSets(final Map params) { + if (!params.isEmpty()) { return prepareErrorResponseXml(OaiError.badArgument); } + + final Document doc = genericOaiResponse(OaiVerb.LIST_SETS.getVerb()); + final Element dataNode = doc.getRootElement().addElement(OaiVerb.LIST_SETS.getVerb()); + + for (final OaiSet oaiSet : this.oaiService.listSets()) { + final Element setNode = dataNode.addElement("set"); + setNode.addElement("setSpec").setText(oaiSet.getSetSpec()); + setNode.addElement("setName").setText(oaiSet.getSetName()); + setNode.addElement("setDescription").setText(oaiSet.getDescription()); + } + return doc.asXML(); + + } + + private String oaiGetRecord(final Map params) { + final String prefix = params.remove("metadataPrefix"); + final String identifier = params.remove("identifier"); + if (!params.isEmpty() || StringUtils.isAnyBlank(prefix, identifier)) { return prepareErrorResponseXml(OaiError.badArgument); } + + final OaiRecord record = this.oaiService.getRecord(identifier, prefix); + if (record == null) { return prepareErrorResponseXml(OaiError.idDoesNotExist); } + + final Document doc = genericOaiResponse(OaiVerb.GET_RECORD.getVerb()); + final Element dataNode = doc.getRootElement().addElement(OaiVerb.GET_RECORD.getVerb()); + + insertSingleRecord(dataNode, record); + + return doc.asXML(); + } + + private String oaiListRecords(final Map params) { + final OaiPage page; + if (params.containsKey("resumptionToken")) { + final String resumptionToken = params.remove("resumptionToken"); + if (!params.isEmpty()) { return prepareErrorResponseXml(OaiError.badArgument); } + page = this.oaiService.listRecords(resumptionToken); + } else { + + final String metadataPrefix = params.remove("metadataPrefix"); + final String from = params.remove("from"); + final String until = params.remove("until"); + final String set = params.remove("set"); + + if (!StringUtils.isNotBlank(metadataPrefix) || !this.oaiService.verifySet(set)) { return prepareErrorResponseXml(OaiError.badArgument); } + page = this.oaiService.listRecords(metadataPrefix, set, from, until); + } + + final Document doc = genericOaiResponse(OaiVerb.LIST_RECORDS.getVerb()); + final Element dataNode = doc.getRootElement().addElement(OaiVerb.LIST_RECORDS.getVerb()); + + page.getList().forEach(record -> insertSingleRecord(dataNode, record)); + + insertResumptionToken(dataNode, page); + + return doc.asXML(); + } + + private void insertSingleRecord(final Element parentNode, final OaiRecord record) { + final Element recordNode = parentNode.addElement("record"); + insertRecordHeader(recordNode, record); + try { + final Document doc2 = DocumentHelper.parseText(record.getBody()); + recordNode.addElement("metadata").add(doc2.getRootElement()); + } catch (final DocumentException e) { + log.warn("Error parsing record: " + record.getBody()); + } + } + + private String oaiListIdentifiers(final Map params) { + + final OaiPage page; + + if (params.containsKey("resumptionToken")) { + final String resumptionToken = params.remove("resumptionToken"); + if (!params.isEmpty()) { return prepareErrorResponseXml(OaiError.badArgument); } + page = this.oaiService.listRecords(resumptionToken); + } else { + final String metadataPrefix = params.remove("metadataPrefix"); + final String from = params.remove("from"); + final String until = params.remove("until"); + final String set = params.remove("set"); + + if (!StringUtils.isNotBlank(metadataPrefix) || !this.oaiService.verifySet(set)) { return prepareErrorResponseXml(OaiError.badArgument); } + + page = this.oaiService.listRecords(metadataPrefix, set, from, until); + } + + final Document doc = genericOaiResponse(OaiVerb.LIST_IDENTIFIERS.getVerb()); + final Element dataNode = doc.getRootElement().addElement(OaiVerb.LIST_IDENTIFIERS.getVerb()); + + page.getList().forEach(r -> insertRecordHeader(dataNode, r)); + + insertResumptionToken(dataNode, page); + + return doc.asXML(); + } + + private void insertRecordHeader(final Element parentNode, final OaiRecord r) { + final Element headerNode = parentNode.addElement("header"); + headerNode.addElement("identifier").setText(r.getId()); + headerNode.addElement("datestamp").setText(DateUtils.calculate_ISO8601(r.getDate())); + if (StringUtils.isNotBlank(r.getOaiSet())) { + headerNode.addElement("setSpec").setText(r.getOaiSet()); + } + } + + private void insertResumptionToken(final Element parentNode, final OaiPage page) { + if (StringUtils.isNotBlank(page.getResumptionToken())) { + final Element tokenNode = parentNode.addElement("resumptionToken"); + tokenNode.addAttribute("completeListSize", Long.toString(page.getTotal())); + tokenNode.addAttribute("cursor", Long.toString(page.getCursor())); + tokenNode.setText(page.getResumptionToken()); + } + } + + private Map cleanParameters(final Map startParams) { + final HashMap params = new HashMap<>(); + final Iterator iter = startParams.entrySet().iterator(); + while (iter.hasNext()) { + final Entry entry = (Entry) iter.next(); + final String key = entry.getKey().toString(); + final String[] arr = (String[]) entry.getValue(); + if (arr.length == 0) { return null; } + final String value = arr[0]; + if ("verb".equals(key)) { + params.put("verb", value); + } else if ("from".equals(key)) { + params.put("from", value); + } else if ("until".equals(key)) { + params.put("until", value); + } else if ("metadataPrefix".equals(key)) { + params.put("metadataPrefix", value); + } else if ("identifier".equals(key)) { + params.put("identifier", value); + } else if ("set".equals(key)) { + params.put("set", value); + } else if ("resumptionToken".equals(key)) { + params.put("resumptionToken", value); + } else { + return null; + } + } + return params; + } + + private Document genericOaiResponse(final String verb) { + try (InputStream is = getClass().getResourceAsStream("/oai/oai_response.xml")) { + final Document doc = new SAXReader().read(is); + doc.selectSingleNode("//*[local-name() = 'responseDate']").setText(DateUtils.now_ISO8601()); + doc.selectSingleNode("//*[local-name() = 'request']").setText(oaiConf.getBaseUrl()); + if (StringUtils.isNotBlank(verb)) { + doc.selectSingleNode("//*[local-name() = 'request']/@verb").setText(verb); + } + return doc; + } catch (final DocumentException | IOException e) { + throw new RuntimeException("Error generataing oai response", e); + } + } + + private String prepareErrorResponseXml(final OaiError error) { + final Document doc = genericOaiResponse(null); + final Element errorNode = doc.getRootElement().addElement("error"); + errorNode.addAttribute("code", error.name()); + errorNode.setText(error.getMessage()); + return doc.asXML(); + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/OaiService.java b/src/main/java/eu/dnetlib/apps/oai/OaiService.java new file mode 100644 index 0000000..627529e --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/OaiService.java @@ -0,0 +1,50 @@ +package eu.dnetlib.apps.oai; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import eu.dnetlib.apps.oai.domain.OaiMetadataFormat; +import eu.dnetlib.apps.oai.domain.OaiPage; +import eu.dnetlib.apps.oai.domain.OaiRecord; +import eu.dnetlib.apps.oai.domain.OaiSet; + +@Service +public class OaiService { + + public boolean verifySet(final String set) { + // TODO Auto-generated method stub + return false; + } + + public OaiPage listRecords(final String resumptionToken) { + // TODO Auto-generated method stub + return null; + } + + public OaiPage listRecords(final String metadataPrefix, final String set, final String from, final String until) { + // TODO Auto-generated method stub + return null; + } + + public List listMetadataFormats(final String id) { + // TODO Auto-generated method stub + return null; + } + + public List listMetadataFormats() { + // TODO Auto-generated method stub + return null; + } + + public OaiRecord getRecord(final String identifier, final String prefix) { + // TODO Auto-generated method stub + return null; + } + + public OaiSet[] listSets() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/domain/OaiError.java b/src/main/java/eu/dnetlib/apps/oai/domain/OaiError.java new file mode 100644 index 0000000..6baf684 --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/domain/OaiError.java @@ -0,0 +1,26 @@ +package eu.dnetlib.apps.oai.domain; + +public enum OaiError { + + badArgument( + "The request includes illegal arguments, is missing required arguments, includes a repeated argument, or values for arguments have an illegal syntax."), + badVerb("Value of the verb argument is not a legal OAI-PMH verb, the verb argument is missing, or the verb argument is repeated."), + cannotDisseminateFormat( + "The metadata format identified by the value given for the metadataPrefix argument is not supported by the item or by the repository."), + idDoesNotExist("The value of the identifier argument is unknown or illegal in this repository."), + noMetadataFormats("There are no metadata formats available for the specified item."), + noSetHierarchy("The repository does not support sets."), + noRecordsMatch("The combination of the values of the from, until, set and metadataPrefix arguments results in an empty list."), + badResumptionToken("The value of the resumptionToken argument is invalid or expired."); + + private final String message; + + OaiError(final String message) { + this.message = message; + } + + public final String getMessage() { + return message; + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/domain/OaiMetadataFormat.java b/src/main/java/eu/dnetlib/apps/oai/domain/OaiMetadataFormat.java new file mode 100644 index 0000000..bfc4b85 --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/domain/OaiMetadataFormat.java @@ -0,0 +1,49 @@ +package eu.dnetlib.apps.oai.domain; + +import java.io.Serializable; + +public class OaiMetadataFormat implements Serializable { + + private static final long serialVersionUID = -3225471556469204065L; + + private String metadataPrefix; + + private String metadataSchema; + + private String metadataNamespace; + + private String xslt; + + public String getMetadataPrefix() { + return this.metadataPrefix; + } + + public void setMetadataPrefix(final String metadataPrefix) { + this.metadataPrefix = metadataPrefix; + } + + public String getMetadataSchema() { + return this.metadataSchema; + } + + public void setMetadataSchema(final String metadataSchema) { + this.metadataSchema = metadataSchema; + } + + public String getMetadataNamespace() { + return this.metadataNamespace; + } + + public void setMetadataNamespace(final String metadataNamespace) { + this.metadataNamespace = metadataNamespace; + } + + public String getXslt() { + return this.xslt; + } + + public void setXslt(final String xslt) { + this.xslt = xslt; + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/domain/OaiPage.java b/src/main/java/eu/dnetlib/apps/oai/domain/OaiPage.java new file mode 100644 index 0000000..28f36f3 --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/domain/OaiPage.java @@ -0,0 +1,50 @@ +package eu.dnetlib.apps.oai.domain; + +import java.io.Serializable; +import java.util.List; + +public class OaiPage implements Serializable { + + private static final long serialVersionUID = -7512951692582271344L; + + private List list; + + private long total; + + private long cursor; + + private String resumptionToken; + + public List getList() { + return list; + } + + public void setList(final List list) { + this.list = list; + } + + public long getTotal() { + return total; + } + + public void setTotal(final long total) { + this.total = total; + } + + public long getCursor() { + return cursor; + } + + public void setCursor(final long cursor) { + this.cursor = cursor; + } + + public String getResumptionToken() { + return resumptionToken; + } + + public void setResumptionToken(final String resumptionToken) { + this.resumptionToken = resumptionToken; + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/domain/OaiPageRequest.java b/src/main/java/eu/dnetlib/apps/oai/domain/OaiPageRequest.java new file mode 100644 index 0000000..e4c3ba0 --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/domain/OaiPageRequest.java @@ -0,0 +1,114 @@ +package eu.dnetlib.apps.oai.domain; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; + +import eu.dnetlib.apps.oai.utils.DateUtils; + +public class OaiPageRequest { + + private static final String SEPARATOR = "@@@"; + + private String metadataPrefix; + private String set; + private LocalDate from; + private LocalDate until; + private int pageNumber; + private int pageSize; + + private OaiPageRequest(final String metadataPrefix, final String set, final LocalDate from, final LocalDate until, final int pageNumber, + final int pageSize) { + super(); + this.metadataPrefix = metadataPrefix; + this.set = set; + this.from = from; + this.until = until; + this.pageNumber = pageNumber; + this.pageSize = pageSize; + } + + public static OaiPageRequest prepareRequest(final String metadataPrefix, + final String set, + final LocalDate from, + final LocalDate until, + final int pageNumber, + final int pageSize) { + return new OaiPageRequest(metadataPrefix, set, from, until, pageNumber, pageSize); + } + + public static OaiPageRequest fromResumptionToken(final String resumptionToken) { + + final String[] arr = StringUtils.split(new String(Base64.decodeBase64(resumptionToken)), SEPARATOR); + + if (arr.length != 6) { throw new RuntimeException("Invalid token"); } + final String metadataPrefix = arr[0]; + final String set = arr[1]; + final LocalDate from = DateUtils.parseDate(arr[2]); + final LocalDate until = DateUtils.parseDate(arr[3]); + final int pageNumber = Integer.parseInt(arr[4]); + final int pageSize = Integer.parseInt(arr[5]); + + return new OaiPageRequest(metadataPrefix, set, from, until, pageNumber, pageSize); + } + + public String getMetadataPrefix() { + return metadataPrefix; + } + + public void setMetadataPrefix(final String metadataPrefix) { + this.metadataPrefix = metadataPrefix; + } + + public String getSet() { + return set; + } + + public void setSet(final String set) { + this.set = set; + } + + public LocalDate getFrom() { + return from; + } + + public void setFrom(final LocalDate from) { + this.from = from; + } + + public LocalDate getUntil() { + return until; + } + + public void setUntil(final LocalDate until) { + this.until = until; + } + + public String nextResumptionToken() { + final List list = Arrays.asList(metadataPrefix, set, from.toString(), until.toString(), Integer.toString(pageNumber + 1), Integer + .toString(pageSize)); + + final String s = StringUtils.join(list, SEPARATOR); + + return Base64.encodeBase64URLSafeString(s.getBytes()); + } + + public int getPageNumber() { + return pageNumber; + } + + public void setPageNumber(final int pageNumber) { + this.pageNumber = pageNumber; + } + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(final int pageSize) { + this.pageSize = pageSize; + } +} diff --git a/src/main/java/eu/dnetlib/apps/oai/domain/OaiRecord.java b/src/main/java/eu/dnetlib/apps/oai/domain/OaiRecord.java new file mode 100644 index 0000000..fdcf95f --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/domain/OaiRecord.java @@ -0,0 +1,59 @@ +package eu.dnetlib.apps.oai.domain; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +public class OaiRecord implements Serializable { + + private static final long serialVersionUID = -8383104201424481929L; + + private String id; + + private String body; + + private LocalDateTime date; + + private String oaiSet; + + public String getId() { + return this.id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getBody() { + return this.body; + } + + public void setBody(final String body) { + this.body = body; + } + + public LocalDateTime getDate() { + return this.date; + } + + public void setDate(final LocalDateTime date) { + this.date = date; + } + + public String getOaiSet() { + return this.oaiSet; + } + + public void setOaiSet(final String oaiSet) { + this.oaiSet = oaiSet; + } + + public List getSets() { + return Arrays.asList(this.oaiSet); + } + + public boolean isDeleted() { + return false; + } +} diff --git a/src/main/java/eu/dnetlib/apps/oai/domain/OaiSet.java b/src/main/java/eu/dnetlib/apps/oai/domain/OaiSet.java new file mode 100644 index 0000000..a61fea9 --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/domain/OaiSet.java @@ -0,0 +1,53 @@ +package eu.dnetlib.apps.oai.domain; + +import java.io.Serializable; + +public class OaiSet implements Serializable { + + private static final long serialVersionUID = 1995486356252936048L; + + private String setSpec; + + private String setName; + + private String description; + + private String dsId; + + public String getSetSpec() { + return this.setSpec; + } + + public void setSetSpec(final String setSpec) { + this.setSpec = setSpec; + } + + public String getSetName() { + return this.setName; + } + + public void setSetName(final String setName) { + this.setName = setName; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public String getDsId() { + return this.dsId; + } + + public void setDsId(final String dsId) { + this.dsId = dsId; + } + + public static long getSerialversionuid() { + return serialVersionUID; + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/domain/OaiVerb.java b/src/main/java/eu/dnetlib/apps/oai/domain/OaiVerb.java new file mode 100644 index 0000000..ac4e2e5 --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/domain/OaiVerb.java @@ -0,0 +1,36 @@ +package eu.dnetlib.apps.oai.domain; + +import org.apache.commons.lang3.StringUtils; + +public enum OaiVerb { + + IDENTIFY("Identify"), + LIST_IDENTIFIERS("ListIdentifiers"), + LIST_RECORDS("ListRecords"), + LIST_METADATA_FORMATS("ListMetadataFormats"), + LIST_SETS("ListSets"), + GET_RECORD("GetRecord"), + UNSUPPORTED_VERB(""); + + private final String verb; + + public static OaiVerb validate(final String verb) { + + if (StringUtils.isBlank(verb)) { return UNSUPPORTED_VERB; } + + for (final OaiVerb v : OaiVerb.values()) { + if (v.getVerb().equalsIgnoreCase(verb)) { return v; } + } + + return UNSUPPORTED_VERB; + } + + private OaiVerb(final String verb) { + this.verb = verb; + } + + public String getVerb() { + return verb; + } + +} diff --git a/src/main/java/eu/dnetlib/apps/oai/utils/DateUtils.java b/src/main/java/eu/dnetlib/apps/oai/utils/DateUtils.java new file mode 100644 index 0000000..3f2310c --- /dev/null +++ b/src/main/java/eu/dnetlib/apps/oai/utils/DateUtils.java @@ -0,0 +1,39 @@ +package eu.dnetlib.apps.oai.utils; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.TimeZone; + +public class DateUtils { + + private static final DateTimeFormatter DATEFORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.getDefault()); + + private static final DateTimeFormatter ISO8601FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); + + public static LocalDate parseDate(final String s) { + return LocalDate.parse(s, DATEFORMAT); + } + + public static String calculate_ISO8601(final LocalDateTime time) { + final String result = time.format(ISO8601FORMAT); + return result.substring(0, result.length() - 2) + ":" + result.substring(result.length() - 2); + } + + public static String calculate_ISO8601(final long l) { + return calculate_ISO8601(LocalDateTime.ofInstant(Instant.ofEpochMilli(l), TimeZone + .getDefault() + .toZoneId())); + } + + public static long now() { + return Instant.now().toEpochMilli(); + } + + public static String now_ISO8601() { + return calculate_ISO8601(now()); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..2178965 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,17 @@ +spring.application.name=dnet-oai-server + +oai.server.baseUrl = http://localhost:8080/oai +oai.server.repositoryName = TEST repository +oai.server.adminEmail = test@test + +spring.datasource.url=jdbc:postgresql://localhost:5432/oai_server_test +spring.datasource.username= +spring.datasource.password= + +spring.jpa.hibernate.ddl-auto = validate +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.open-in-view=true +spring.jpa.properties.hibernate.show_sql=false +spring.jpa.properties.hibernate.use_sql_comments=false +spring.jpa.properties.hibernate.format_sql=false diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/eu/dnetlib/apps/oai/OaiServerApplicationTests.java b/src/test/java/eu/dnetlib/apps/oai/OaiServerApplicationTests.java new file mode 100644 index 0000000..59ec099 --- /dev/null +++ b/src/test/java/eu/dnetlib/apps/oai/OaiServerApplicationTests.java @@ -0,0 +1,12 @@ +package eu.dnetlib.apps.oai; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class OaiServerApplicationTests { + + @Test + void contextLoads() {} + +}