diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/AuthorDTO.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/AuthorDTO.java new file mode 100644 index 0000000000..5c25c9d703 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/AuthorDTO.java @@ -0,0 +1,10 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.dto; + +public class AuthorDTO { + + public String name; + + public String type; + + public String identifier; +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/FunderRequestDTO.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/FunderRequestDTO.java new file mode 100644 index 0000000000..6a07f04f95 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/FunderRequestDTO.java @@ -0,0 +1,10 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.dto; + +public class FunderRequestDTO { + + public String name; + + public String type; + + public String grantName; +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/FunderResponseDTO.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/FunderResponseDTO.java new file mode 100644 index 0000000000..fd7436e381 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/FunderResponseDTO.java @@ -0,0 +1,8 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.dto; + +public class FunderResponseDTO { + + public String name; + + public String type; +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/InformationContentEntityResponseDTO.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/InformationContentEntityResponseDTO.java new file mode 100644 index 0000000000..6dc344773a --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/InformationContentEntityResponseDTO.java @@ -0,0 +1,19 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.dto; + +import java.util.ArrayList; +import java.util.List; + +public class InformationContentEntityResponseDTO { + + public String internalIdentifier; + + public String name; + + public String datePublished; + + public List authors = new ArrayList<>(); + + public List fundings = new ArrayList<>(); + + public List funders = new ArrayList<>(); +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/SoftwareRequestDTO.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/SoftwareRequestDTO.java new file mode 100644 index 0000000000..6fe0494055 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/SoftwareRequestDTO.java @@ -0,0 +1,30 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.dto; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class SoftwareRequestDTO { + + public String internalIdentifier; + + @JsonProperty(required = true) + public String name; + + public String datePublished; + + public List authors = new ArrayList<>(); + + public List fundings = new ArrayList<>(); + + public List funders = new ArrayList<>(); + + public String version; + + public String description; + + public List identifiers = new ArrayList<>(); + + public String keywords; +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/SoftwareResponseDTO.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/SoftwareResponseDTO.java new file mode 100644 index 0000000000..ca53382efc --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/dto/SoftwareResponseDTO.java @@ -0,0 +1,23 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.dto; + +import java.util.ArrayList; +import java.util.List; + +public class SoftwareResponseDTO extends InformationContentEntityResponseDTO { + + public String version; + + public String description; + + public List identifiers = new ArrayList<>(); + + public String sameAs; + + public String url; + + public String keywords; + + public String isPartOf; + + public String hasPart; +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/software/SoftwareController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/software/SoftwareController.java new file mode 100644 index 0000000000..eab83afd4f --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/software/SoftwareController.java @@ -0,0 +1,377 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.software; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest; +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.api.dto.SoftwareRequestDTO; +import edu.cornell.mannlib.vitro.webapp.controller.api.dto.SoftwareResponseDTO; +import edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.SparqlQueryApiExecutor; +import edu.cornell.mannlib.vitro.webapp.controller.api.utility.InformationContentEntityResponseUtility; +import edu.cornell.mannlib.vitro.webapp.controller.api.utility.InsertQueryBuilder; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.ontology.OntModel; + +@WebServlet(name = "softwareController", urlPatterns = {"/software", "/software/*"}, loadOnStartup = 5) +public class SoftwareController extends VitroHttpServlet { + + private static final Log log = LogFactory.getLog(SoftwareController.class); + + private final String FIND_ALL_QUERY = + "PREFIX rdf: \n" + + "PREFIX rdfs: \n" + + "PREFIX xsd: \n" + + "PREFIX owl: \n" + + "PREFIX swrl: \n" + + "PREFIX swrlb: \n" + + "PREFIX vitro: \n" + + "PREFIX bibo: \n" + + "PREFIX c4o: \n" + + "PREFIX cito: \n" + + "PREFIX dcterms: \n" + + "PREFIX event: \n" + + "PREFIX fabio: \n" + + "PREFIX foaf: \n" + + "PREFIX geo: \n" + + "PREFIX obo: \n" + + "PREFIX vivo: \n" + + "PREFIX vcard: \n" + + "\n" + + "SELECT ?software ?label ?author ?authorType ?authorIdentifier ?datePublished ?funding ?funder ?keywords " + + "?version ?abstract ?identifier ?doi ?sameAs ?url ?funderType\n" + + "WHERE\n" + + "{\n" + + " ?software rdf:type obo:ERO_0000071\n" + + " OPTIONAL { ?software rdfs:label ?label }\n" + + " OPTIONAL { ?software vivo:relatedBy ?relatedObject .\n" + + " ?relatedObject vitro:mostSpecificType vivo:Authorship .\n" + + " OPTIONAL { ?relatedObject vivo:relates ?authorObject .\n" + + " OPTIONAL { ?authorObject rdfs:label ?author }\n" + + " OPTIONAL { ?authorObject vitro:mostSpecificType ?authorType }\n" + + " OPTIONAL { ?authorObject vivo:orcidId ?authorIdentifier }\n" + + " }\n" + + " FILTER (?author != ?label)\n" + + " }\n" + + " OPTIONAL { ?software vivo:dateTimeValue ?dateObject .\n" + + " OPTIONAL { ?dateObject vivo:dateTime ?datePublished }\n" + + " }\n" + + " OPTIONAL { ?software vivo:informationResourceSupportedBy ?fundingObject .\n" + + " OPTIONAL { ?fundingObject vivo:assignedBy ?funderObject ." + + " OPTIONAL { ?funderObject rdfs:label ?funder }\n" + + " OPTIONAL { ?funderObject vitro:mostSpecificType ?funderType }\n" + + " }\n" + + " OPTIONAL { ?fundingObject rdfs:label ?funding }\n" + + " }\n" + + " OPTIONAL { ?software vivo:freetextKeyword ?keywords }\n" + + " OPTIONAL { ?software obo:ERO_0000072 ?version }\n" + + " OPTIONAL { ?software bibo:abstract ?abstract }\n" + + " OPTIONAL { ?software obo:BFO_0000050 ?isPartOf }\n" + + " OPTIONAL { ?software obo:BFO_0000050 ?hasPart }\n" + + " OPTIONAL { ?software vivo:swhid ?identifier }\n" + + " OPTIONAL { ?software bibo:doi ?doi }\n" + + " OPTIONAL { ?software owl:sameAs ?sameAsObject .\n" + + " OPTIONAL { ?sameAsObject rdfs:label ?sameAs }\n" + + " }\n" + + " OPTIONAL { ?software obo:ARG_2000028 ?contactInfo .\n" + + " OPTIONAL { ?contactInfo vcard:hasURL ?url }\n" + + " }\n" + + "}\n"; + + private final String FIND_BY_ID_QUERY_TEMPLATE = + "PREFIX rdf: \n" + + "PREFIX rdfs: \n" + + "PREFIX xsd: \n" + + "PREFIX owl: \n" + + "PREFIX swrl: \n" + + "PREFIX swrlb: \n" + + "PREFIX vitro: \n" + + "PREFIX bibo: \n" + + "PREFIX c4o: \n" + + "PREFIX cito: \n" + + "PREFIX dcterms: \n" + + "PREFIX event: \n" + + "PREFIX fabio: \n" + + "PREFIX foaf: \n" + + "PREFIX geo: \n" + + "PREFIX obo: \n" + + "PREFIX vivo: \n" + + "PREFIX vcard: \n" + + "\n" + + "SELECT ?software ?label ?author ?authorType ?authorIdentifier ?datePublished ?funding ?funder ?keywords " + + "?version ?abstract ?identifier ?doi ?sameAs ?url ?funderType\n" + + "WHERE\n" + + "{\n" + + " BIND (<%s> AS ?software)\n" + + " ?software rdf:type obo:ERO_0000071\n" + + " OPTIONAL { ?software rdfs:label ?label }\n" + + " OPTIONAL { ?software vivo:relatedBy ?relatedObject .\n" + + " ?relatedObject vitro:mostSpecificType vivo:Authorship .\n" + + " OPTIONAL { ?relatedObject vivo:relates ?authorObject .\n" + + " OPTIONAL { ?authorObject rdfs:label ?author }\n" + + " OPTIONAL { ?authorObject vitro:mostSpecificType ?authorType }\n" + + " OPTIONAL { ?authorObject vivo:orcidId ?authorIdentifier }\n" + + " }\n" + + " FILTER (?author != ?label)\n" + + " }\n" + + " OPTIONAL { ?software vivo:dateTimeValue ?dateObject .\n" + + " OPTIONAL { ?dateObject vivo:dateTime ?datePublished }\n" + + " }\n" + + " OPTIONAL { ?software vivo:informationResourceSupportedBy ?fundingObject .\n" + + " OPTIONAL { ?fundingObject vivo:assignedBy ?funderObject ." + + " OPTIONAL { ?funderObject rdfs:label ?funder }\n" + + " OPTIONAL { ?funderObject vitro:mostSpecificType ?funderType }\n" + + " }\n" + + " OPTIONAL { ?fundingObject rdfs:label ?funding }\n" + + " }\n" + + " OPTIONAL { ?software vivo:freetextKeyword ?keywords }\n" + + " OPTIONAL { ?software obo:ERO_0000072 ?version }\n" + + " OPTIONAL { ?software bibo:abstract ?abstract }\n" + + " OPTIONAL { ?software obo:BFO_0000050 ?isPartOf }\n" + + " OPTIONAL { ?software obo:BFO_0000050 ?hasPart }\n" + + " OPTIONAL { ?software vivo:swhid ?identifier }\n" + + " OPTIONAL { ?software bibo:doi ?doi }\n" + + " OPTIONAL { ?software owl:sameAs ?sameAsObject .\n" + + " OPTIONAL { ?sameAsObject rdfs:label ?sameAs }\n" + + " }\n" + + " OPTIONAL { ?software obo:ARG_2000028 ?contactInfo .\n" + + " OPTIONAL { ?contactInfo vcard:hasURL ?url }\n" + + " }\n" + + "}\n"; + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String pathInfo = req.getPathInfo(); + String queryString = (pathInfo != null && pathInfo.length() > 1) + ? String.format(FIND_BY_ID_QUERY_TEMPLATE, + IndividualApiSparqlUtility.buildIndividualUri(pathInfo.substring(1))) + : FIND_ALL_QUERY; + + RDFService rdfService = ModelAccess.on(getServletContext()).getRDFService(); + + try { + SparqlQueryApiExecutor core = + SparqlQueryApiExecutor.instance(rdfService, queryString, "application/sparql-results+json"); + IndividualApiNetworkUtility.handleResponseContentType(req, resp); + + if (IndividualApiNetworkUtility.isJsonRequest(req)) { + List response = handleDTOConversion(core, resp); + + if (response.isEmpty()) { + return; + } + + resp.getWriter().println( + IndividualApiNetworkUtility.serializeToJSON(response.size() == 1 ? response.get(0) : response)); + } else { + core.executeAndFormat(resp.getOutputStream()); + } + } catch (Exception e) { + IndividualApiNetworkUtility.handleException(e, queryString, resp); + } + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (!isAuthorizedToDisplayPage(req, resp, requiredActions(new VitroRequest(req)))) { + return; + } + + IndividualApiCommonCRUDUtility.executeWithTransaction(req, resp, (graphStore, softwareUri) -> { + + SoftwareRequestDTO softwareDTO = null; + try { + softwareDTO = IndividualApiNetworkUtility.parseRequestBody(req, SoftwareRequestDTO.class); + } catch (IOException e) { + try { + IndividualApiNetworkUtility.do400BadRequest("Error while parsing request body.", resp); + } catch (IOException ex) { + log.error("Error while handling exception.", ex); + } + } + + if (Objects.isNull(softwareDTO)) { + return; + } + + String insertSoftwareQuery = buildInsertSoftwareQuery(softwareDTO, softwareUri, ModelAccess.on( + new VitroRequest(req)).getOntModel()); + IndividualApiSparqlUtility.executeUpdate(graphStore, insertSoftwareQuery); + + softwareDTO.internalIdentifier = softwareUri; + try { + resp.setContentType("application/json"); + resp.getWriter().println(IndividualApiNetworkUtility.serializeToJSON(softwareDTO)); + } catch (IOException e) { + try { + IndividualApiNetworkUtility.do400BadRequest("Error while writing response body.", resp); + } catch (IOException ex) { + log.error("Error while handling exception.", ex); + } + } + resp.setStatus(HttpServletResponse.SC_CREATED); + }); + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (!isAuthorizedToDisplayPage(req, resp, requiredActions(new VitroRequest(req)))) { + return; + } + + IndividualApiCommonCRUDUtility.performDeleteOperation(req, resp); + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (!isAuthorizedToDisplayPage(req, resp, requiredActions(new VitroRequest(req)))) { + return; + } + + doDelete(req, resp); + + IndividualApiCommonCRUDUtility.executeWithTransaction(req, resp, (graphStore, softwareUri) -> { + SoftwareRequestDTO softwareDTO; + try { + softwareDTO = IndividualApiNetworkUtility.parseRequestBody(req, SoftwareRequestDTO.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + String insertSoftwareQuery = buildInsertSoftwareQuery(softwareDTO, softwareUri, ModelAccess.on( + new VitroRequest(req)).getOntModel()); + IndividualApiSparqlUtility.executeUpdate(graphStore, insertSoftwareQuery); + + softwareDTO.internalIdentifier = softwareUri; + resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + }); + } + + private String buildInsertSoftwareQuery(SoftwareRequestDTO softwareDTO, String softwareUri, + OntModel ontModel) { + InsertQueryBuilder queryBuilder = new InsertQueryBuilder(); + queryBuilder.startInsertQuery(); + + String defaultNamespace = ConfigurationProperties.getInstance().getProperty("Vitro.defaultNamespace"); + addSoftwareRelatedFields(queryBuilder.getInsertQuery(), softwareDTO, softwareUri); + + queryBuilder + .addPublicationDate(softwareDTO.datePublished, defaultNamespace, softwareUri) + .addAuthors(softwareDTO.authors, defaultNamespace, softwareUri, ontModel) + .addFunding(softwareDTO.fundings, defaultNamespace, softwareUri) + .addFunders(softwareDTO.funders, defaultNamespace, softwareUri); + + queryBuilder.getInsertQuery().append(" }\n").append("}\n"); + + return queryBuilder.build(); + } + + private void addSoftwareRelatedFields(StringBuilder query, SoftwareRequestDTO softwareDTO, String softwareUri) { + query.append("<").append(softwareUri).append("> rdf:type obo:ERO_0000071 ;\n"); + + if (Objects.nonNull(softwareDTO.keywords) && !softwareDTO.keywords.isEmpty()) { + query.append("vivo:freetextKeyword \"").append(StringEscapeUtils.escapeJava(softwareDTO.keywords)) + .append("\" ;\n"); + } + + if (Objects.nonNull(softwareDTO.version) && !softwareDTO.version.isEmpty()) { + query.append("obo:ERO_0000072 \"").append(StringEscapeUtils.escapeJava(softwareDTO.version)) + .append("\" ;\n"); + } + + if (Objects.nonNull(softwareDTO.description) && !softwareDTO.description.isEmpty()) { + query.append("bibo:abstract \"").append(StringEscapeUtils.escapeJava(softwareDTO.description)) + .append("\" ;\n"); + } + + String doiPattern = "^10\\.\\d{4,9}/[-,._;()/:A-Z0-9]+$"; + Pattern compiledPattern = Pattern.compile(doiPattern, Pattern.CASE_INSENSITIVE); + for (String identifier : softwareDTO.identifiers) { + if (compiledPattern.matcher(identifier).matches()) { + query.append("bibo:doi \"").append(StringEscapeUtils.escapeJava(identifier)).append("\" ;\n"); + } else { + query.append("vivo:swhid \"").append(StringEscapeUtils.escapeJava(identifier)).append("\" ;\n"); + } + } + + query.append("rdfs:label \"").append(StringEscapeUtils.escapeJava(softwareDTO.name)).append("\"@en-US .\n"); + } + + private List handleDTOConversion(SparqlQueryApiExecutor core, HttpServletResponse resp) + throws RDFServiceException, IOException { + String sparqlQueryResponse = IndividualApiSparqlUtility.getSparqlQueryResponse(core); + + List> bindings = IndividualApiSparqlUtility.parseBindings(sparqlQueryResponse); + if (bindings.isEmpty()) { + IndividualApiNetworkUtility.do404NotFound("Not found.", resp); + } + + List softwareResponse = new ArrayList<>(); + for (Map binding : bindings) { + Optional existingRecord = + softwareResponse.stream() + .filter(software -> software.internalIdentifier.equals(binding.get("software"))).findFirst(); + if (existingRecord.isPresent()) { + InformationContentEntityResponseUtility.addAuthorToICE(binding, existingRecord.get()); + InformationContentEntityResponseUtility.addFundingToICE(binding, existingRecord.get()); + InformationContentEntityResponseUtility.addFundersToICE(binding, existingRecord.get()); + continue; + } + + SoftwareResponseDTO software = new SoftwareResponseDTO(); + software.internalIdentifier = binding.getOrDefault("software", null); + software.name = binding.getOrDefault("label", null); + software.description = binding.getOrDefault("abstract", null); + software.version = binding.getOrDefault("version", null); + software.sameAs = binding.getOrDefault("sameAs", null); + + String swhid = binding.getOrDefault("identifier", null); + if (Objects.nonNull(swhid)) { + software.identifiers.add(swhid); + } + + String doi = binding.getOrDefault("doi", null); + if (Objects.nonNull(doi)) { + software.identifiers.add(doi); + } + + software.url = binding.getOrDefault("url", null); + software.keywords = binding.getOrDefault("keywords", null); + software.isPartOf = binding.getOrDefault("isPartOf", null); + software.hasPart = binding.getOrDefault("hasPart", null); + + String dateTimeString = binding.getOrDefault("datePublished", null); + if (Objects.nonNull(dateTimeString)) { + software.datePublished = dateTimeString.split("T")[0]; + } + + InformationContentEntityResponseUtility.addAuthorToICE(binding, software); + InformationContentEntityResponseUtility.addFundingToICE(binding, software); + InformationContentEntityResponseUtility.addFundersToICE(binding, software); + + softwareResponse.add(software); + } + + return softwareResponse; + } + + protected AuthorizationRequest requiredActions(VitroRequest vreq) { + return SimplePermission.USE_SPARQL_UPDATE_API.ACTION; + } +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/utility/InformationContentEntityResponseUtility.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/utility/InformationContentEntityResponseUtility.java new file mode 100644 index 0000000000..bf3602df31 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/utility/InformationContentEntityResponseUtility.java @@ -0,0 +1,54 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.utility; + +import java.util.Map; +import java.util.Objects; + +import edu.cornell.mannlib.vitro.webapp.controller.api.dto.AuthorDTO; +import edu.cornell.mannlib.vitro.webapp.controller.api.dto.FunderResponseDTO; +import edu.cornell.mannlib.vitro.webapp.controller.api.dto.InformationContentEntityResponseDTO; + +public class InformationContentEntityResponseUtility { + + public static void addAuthorToICE(Map binding, InformationContentEntityResponseDTO entity) { + if (!Objects.nonNull(binding.get("author"))) { + return; + } + + if (entity.authors.stream().anyMatch(author -> author.name.equals(binding.get("author")))) { + return; + } + + AuthorDTO author = new AuthorDTO(); + author.name = binding.getOrDefault("author", null); + author.type = binding.getOrDefault("authorType", null); + author.identifier = binding.getOrDefault("authorIdentifier", null); + entity.authors.add(author); + } + + public static void addFundingToICE(Map binding, InformationContentEntityResponseDTO entity) { + if (!Objects.nonNull(binding.get("funding"))) { + return; + } + + if (entity.fundings.stream().anyMatch(funding -> funding.equals(binding.get("funding")))) { + return; + } + + entity.fundings.add(binding.get("funding")); + } + + public static void addFundersToICE(Map binding, InformationContentEntityResponseDTO entity) { + if (!Objects.nonNull(binding.get("funder"))) { + return; + } + + if (entity.funders.stream().anyMatch(funder -> funder.name.equals(binding.get("funder")))) { + return; + } + + FunderResponseDTO funder = new FunderResponseDTO(); + funder.name = binding.get("funder"); + funder.type = binding.getOrDefault("funderType", null); + entity.funders.add(funder); + } +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/utility/InsertQueryBuilder.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/utility/InsertQueryBuilder.java new file mode 100644 index 0000000000..a2c85c7f7a --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/api/utility/InsertQueryBuilder.java @@ -0,0 +1,165 @@ +package edu.cornell.mannlib.vitro.webapp.controller.api.utility; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import edu.cornell.mannlib.vitro.webapp.controller.api.dto.AuthorDTO; +import edu.cornell.mannlib.vitro.webapp.controller.api.dto.FunderRequestDTO; +import edu.cornell.mannlib.vitro.webapp.controller.api.software.IndividualApiSparqlUtility; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.ontology.OntModel; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; + +public class InsertQueryBuilder { + + private static final Log log = LogFactory.getLog(InsertQueryBuilder.class); + + private final StringBuilder query = new StringBuilder(); + + public InsertQueryBuilder() { + } + + + public String build() { + return query.toString(); + } + + public InsertQueryBuilder startInsertQuery() { + IndividualApiSparqlUtility.addPrefixClauses(query); + query.append("\n") + .append("INSERT DATA\n") + .append("{\n") + .append("GRAPH ").append("<" + ModelNames.ABOX_ASSERTIONS + ">") + .append("\n") + .append("{\n"); + + return this; + } + + public InsertQueryBuilder addPublicationDate(String dateString, String defaultNamespace, + String documentUri) { + if (dateString != null && !dateString.isEmpty()) { + String dateObjectUri = defaultNamespace + UUID.randomUUID(); + + query.append("<").append(documentUri).append("> vivo:dateTimeValue <") + .append(dateObjectUri).append("> .\n") + .append("<").append(dateObjectUri).append("> rdf:type vivo:DateTimeValue ;\n") + .append("vivo:dateTime \"").append(StringEscapeUtils.escapeJava(dateString)).append("\"^^xsd:date .\n"); + } + + return this; + } + + public InsertQueryBuilder addAuthors(List authors, String defaultNamespace, String documentUri, + OntModel ontModel) { + for (AuthorDTO author : authors) { + String authorUri = null; + boolean personFound = false; + + if (author.identifier != null && !author.identifier.isEmpty() && author.type.endsWith("Person")) { + String checkAuthorQuery = String.format( + "PREFIX rdf: \n" + + "PREFIX vivo: \n" + + "PREFIX foaf: \n" + + "SELECT ?author WHERE { ?author rdf:type foaf:Person . ?author vivo:orcidId \"%s\" . }", + StringEscapeUtils.escapeJava(author.identifier) + ); + + try (QueryExecution qe = QueryExecutionFactory.create(checkAuthorQuery, ontModel)) { + ResultSet results = qe.execSelect(); + + if (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + authorUri = solution.getResource("author").getURI(); + personFound = true; + } + } catch (Exception e) { + log.error("Error when looking for existing person.", e); + } + } + + if (!personFound) { + authorUri = defaultNamespace + UUID.randomUUID(); + + query.append("<").append(authorUri).append("> rdf:type <") + .append(StringEscapeUtils.escapeJava(author.type)).append("> ;\n") + .append("rdfs:label \"").append(StringEscapeUtils.escapeJava(author.name)).append("\"@en-US ;\n"); + + if (author.identifier != null && !author.identifier.isEmpty() && author.type.endsWith("Person")) { + query.append("vivo:orcidId \"").append(StringEscapeUtils.escapeJava(author.identifier)) + .append("\" ;\n"); + } + + query.append(".\n"); + } + + String relatedObjectUri = defaultNamespace + UUID.randomUUID(); + query.append("<").append(documentUri).append("> vivo:relatedBy <").append(relatedObjectUri).append("> .\n") + .append("<").append(relatedObjectUri).append("> rdf:type vivo:Authorship ;\n") + .append("vivo:relates <").append(authorUri).append("> .\n"); + } + + return this; + } + + public InsertQueryBuilder addFunding(List fundings, String defaultNamespace, + String documentUri) { + for (String funding : fundings) { + String funderObjectUri = defaultNamespace + UUID.randomUUID(); + + query.append("<").append(documentUri).append("> vivo:informationResourceSupportedBy <") + .append(funderObjectUri).append("> .\n") + .append("<").append(funderObjectUri).append("> rdf:type vivo:Funding ;\n"); + + query.append("rdfs:label \"").append(StringEscapeUtils.escapeJava(funding)).append("\"@en-US .\n"); + } + + return this; + } + + public InsertQueryBuilder addFunders(List funders, String defaultNamespace, String documentUri) { + for (FunderRequestDTO funder : funders) { + if (Objects.isNull(funder.name) || funder.name.isEmpty() || Objects.isNull(funder.type) || + funder.type.isEmpty()) { + continue; + } + + String grantObjectUri = defaultNamespace + UUID.randomUUID(); + String funderObjectUri = defaultNamespace + UUID.randomUUID(); + + query.append("<").append(documentUri).append("> vivo:informationResourceSupportedBy <") + .append(grantObjectUri).append("> .\n") + .append("<").append(grantObjectUri).append("> rdf:type vivo:Grant ;\n"); + + if (Objects.nonNull(funder.grantName) && !funder.grantName.isEmpty()) { + query.append("rdfs:label \"").append(StringEscapeUtils.escapeJava(funder.grantName)); + } else { + query.append("rdfs:label \"").append(StringEscapeUtils.escapeJava(funder.name)).append(" Grant"); + } + + query + .append("\"@en-US .\n"); + + query + .append("<").append(funderObjectUri).append("> rdf:type <") + .append(StringEscapeUtils.escapeJava(funder.type)).append("> ;\n") + .append("rdfs:label \"").append(StringEscapeUtils.escapeJava(funder.name)).append("\"@en-US .\n") + .append("<").append(grantObjectUri).append("> vivo:assignedBy <") + .append(funderObjectUri).append("> .\n"); + + } + + return this; + } + + public StringBuilder getInsertQuery() { + return query; + } +} diff --git a/home/src/main/resources/rdf/tbox/filegraph/vivo.owl b/home/src/main/resources/rdf/tbox/filegraph/vivo.owl index 1b9d2de125..83878935ec 100644 --- a/home/src/main/resources/rdf/tbox/filegraph/vivo.owl +++ b/home/src/main/resources/rdf/tbox/filegraph/vivo.owl @@ -6397,15 +6397,24 @@ To enable other Gender/Sex codes to be used, this dataproperty has range URI. Th + + + + SWHID + Home page for SoftWare Heritage persistent IDentifiers : https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html + The SoftWare Heritage persistent IDentifier, or SWHID for short, is a PID for software artifacts. It guarantees to remain stable (persistent) over time. + + + + Software - + A general term primarily used for digitally stored data such as computer programs and other kinds of information read and written by computers. - IAO is a planned specification, in SWO is an Information artifact. In eagle-i, we have a need to collect material instances and is it thus currently classified as a material entity. - Microsoft Word is commonly used word processing software. + Aligned with SWO which also defines software and other types of services as subtypes of Information content entity. Microsoft Word is commonly used word processing software. PERSON: Melissa Haendel http://en.wikipedia.org/wiki/Computer_software software @@ -6413,7 +6422,6 @@ To enable other Gender/Sex codes to be used, this dataproperty has range URI. Th - diff --git a/home/src/main/resources/rdf/tbox/firsttime/vitroAnnotations.n3 b/home/src/main/resources/rdf/tbox/firsttime/vitroAnnotations.n3 index 2fcfd609cb..f33e4c0517 100644 --- a/home/src/main/resources/rdf/tbox/firsttime/vitroAnnotations.n3 +++ b/home/src/main/resources/rdf/tbox/firsttime/vitroAnnotations.n3 @@ -1748,6 +1748,18 @@ bibo:doi vitro:selectFromExistingAnnot "true"^^xsd:boolean . +vivo:swhid + vitro:displayLimitAnnot + "1"^^xsd:int ; + vitro:displayRankAnnot + "5"^^xsd:int ; + vitro:hiddenFromDisplayBelowRoleLevelAnnot + ; + vitro:hiddenFromPublishBelowRoleLevelAnnot + ; + vitro:inPropertyGroupAnnot + . + obo:ERO_0000070 ## inverse of ERO_0000031 # vitro:displayLimitAnnot # "2"^^xsd:int ;